org.stanwood.media.Controller.java Source code

Java tutorial

Introduction

Here is the source code for org.stanwood.media.Controller.java

Source

/*
 *  Copyright (C) 2008  John-Paul.Stanford <dev@stanwood.org.uk>
 *
 *  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 3 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, see <http://www.gnu.org/licenses/>.
 */
package org.stanwood.media;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.stanwood.media.actions.IAction;
import org.stanwood.media.actions.command.ExecuteSystemCommandActionInfo;
import org.stanwood.media.actions.podcast.PodCastActionInfo;
import org.stanwood.media.actions.rename.RenameActionInfo;
import org.stanwood.media.actions.seendb.DatabaseSeenDatabase;
import org.stanwood.media.actions.seendb.FileSeenDatabase;
import org.stanwood.media.actions.seendb.ISeenDatabase;
import org.stanwood.media.actions.seendb.SeenDBException;
import org.stanwood.media.actions.seendb.SeenEntry;
import org.stanwood.media.extensions.ExtensionInfo;
import org.stanwood.media.extensions.ExtensionType;
import org.stanwood.media.info.IMediaFileInfo;
import org.stanwood.media.info.IVideoFileInfo;
import org.stanwood.media.info.MediaFileInfoFetcher;
import org.stanwood.media.logging.StanwoodException;
import org.stanwood.media.model.Mode;
import org.stanwood.media.progress.NullProgressMonitor;
import org.stanwood.media.setup.ConfigException;
import org.stanwood.media.setup.ConfigReader;
import org.stanwood.media.setup.DBResource;
import org.stanwood.media.setup.Plugin;
import org.stanwood.media.setup.WatchDirConfig;
import org.stanwood.media.source.HybridFilmSourceInfo;
import org.stanwood.media.source.ISource;
import org.stanwood.media.source.TagChimpSourceInfo;
import org.stanwood.media.source.xbmc.XBMCAddon;
import org.stanwood.media.source.xbmc.XBMCAddonManager;
import org.stanwood.media.source.xbmc.XBMCException;
import org.stanwood.media.source.xbmc.XBMCSource;
import org.stanwood.media.source.xbmc.XBMCSourceInfo;
import org.stanwood.media.source.xbmc.updater.IConsole;
import org.stanwood.media.store.IStore;
import org.stanwood.media.store.SapphireStoreInfo;
import org.stanwood.media.store.db.DatabaseStoreInfo;
import org.stanwood.media.store.db.FileDatabaseStoreInfo;
import org.stanwood.media.store.memory.MemoryStoreInfo;
import org.stanwood.media.store.mp4.MP4ITunesStoreInfo;
import org.stanwood.media.store.mp4.itunes.RemoteMacOSXItunesStoreInfo;
import org.stanwood.media.store.xmlstore.XMLStore2Info;

/**
 * The controller is used to control access to the stores and and sources. This
 * is a singleton class, and just first be setup using the @see
 * initWithDefaults() or @see initFromConfigFile() methods. From then on,
 * getInstance() can be called to a access the methods used to control stores
 * and sources.
 */
public class Controller {

    private final static Log log = LogFactory.getLog(Controller.class);

    private ConfigReader configReader = null;

    private Map<File, MediaDirectory> mediaDirs = new HashMap<File, MediaDirectory>();

    private List<ExtensionInfo<? extends ISource>> pluginSources = new ArrayList<ExtensionInfo<? extends ISource>>();
    private List<ExtensionInfo<? extends IStore>> pluginStores = new ArrayList<ExtensionInfo<? extends IStore>>();
    private List<ExtensionInfo<? extends IAction>> pluginActions = new ArrayList<ExtensionInfo<? extends IAction>>();

    private boolean testMode;

    private ISeenDatabase seenDb;

    private static MediaFileInfoFetcher fileInfoFetcher;

    private static XBMCAddonManager xbmcMgr;

    /**
     * The constructor
     *
     * @param config The parsed configuration
     */
    public Controller(ConfigReader config) {
        this.configReader = config;
    }

    /**
     * Used to get the database resources
     * @return the database resources
     */
    public Map<String, DBResource> getDatabaseResources() {
        return configReader.getDatabaseResources();
    }

    /**
     * Used to setup the controller ready for use
     * @param testMode If true then test mode is active and no changes are to be written to disk
     * @throws ConfigException Thrown if their is a problem reading the configuration
     */
    public void init(boolean testMode) throws ConfigException {
        if (fileInfoFetcher == null) {
            try {
                fileInfoFetcher = new MediaFileInfoFetcher(getNativeFolder());
            } catch (StanwoodException e) {
                throw new ConfigException(Messages.getString("Controller.UNABLE_SETUP_FILE_FILE_INFO"), e); //$NON-NLS-1$
            }
        }
        if (xbmcMgr == null) {
            try {
                setXBMCAddonManager(new XBMCAddonManager(configReader));
                if (getXBMCAddonManager().isFirstTime()) {
                    getXBMCAddonManager().getUpdater().update(new IConsole() {
                        @Override
                        public void error(String error) {
                            log.error(error);
                        }

                        @Override
                        public void info(String info) {
                            log.info(info);
                        }
                    });
                }
                // xbmcMgr.getUpdater().update();
            } catch (XBMCException e) {
                log.error(e.getMessage(), e);
            }
        }
        if (xbmcMgr == null) {
            log.fatal(Messages.getString("Controller.UNABLE_TO_READ_XBMC_ADDONS")); //$NON-NLS-1$
            System.exit(2);
        }
        this.testMode = testMode;
        registerInbuild();
        registerPlugins();
    }

    /**
     * Reload the sources, this can be used when the XBMC addons have changed
     * @throws ConfigException Thrown if their is a problem
     */
    public synchronized void reloadSources() throws ConfigException {
        pluginSources = new ArrayList<ExtensionInfo<? extends ISource>>();
        pluginSources.add(new TagChimpSourceInfo());
        pluginSources.add(new HybridFilmSourceInfo());

        try {
            XBMCAddonManager mgr = getXBMCAddonManager();
            for (String addonId : getXBMCAddonManager().listAddons()) {
                XBMCAddon addon = mgr.getAddon(addonId);
                if (addon.hasScrapers()) {
                    pluginSources.add(new XBMCSourceInfo(mgr, addon));
                }
            }
        } catch (XBMCException e) {
            throw new ConfigException(Messages.getString("Controller.UNABLE_REGISTER_ADDONS"), e); //$NON-NLS-1$
        }

    }

    private void registerInbuild() throws ConfigException {
        reloadSources();

        pluginStores.add(new SapphireStoreInfo());
        pluginStores.add(new MemoryStoreInfo());
        pluginStores.add(new MP4ITunesStoreInfo());
        pluginStores.add(new XMLStore2Info());
        pluginStores.add(new DatabaseStoreInfo());
        pluginStores.add(new FileDatabaseStoreInfo());
        pluginStores.add(new RemoteMacOSXItunesStoreInfo());

        pluginActions.add(new ExecuteSystemCommandActionInfo());
        pluginActions.add(new PodCastActionInfo());
        pluginActions.add(new RenameActionInfo());
    }

    @SuppressWarnings("unchecked")
    private void registerPlugins() throws ConfigException {
        for (Plugin plugin : configReader.getPlugins()) {
            try {
                Class<?> clazz;
                if (plugin.getJar() != null) {
                    URL url = new URL("jar:file:" + plugin.getJar() + "!/"); //$NON-NLS-1$//$NON-NLS-2$
                    URLClassLoader clazzLoader = new URLClassLoader(new URL[] { url });
                    clazz = clazzLoader.loadClass(plugin.getPluginClass());
                } else {
                    clazz = Class.forName(plugin.getPluginClass());
                }
                Class<? extends ExtensionInfo<?>> targetClass = (Class<? extends ExtensionInfo<?>>) clazz
                        .asSubclass(ExtensionInfo.class);
                ExtensionInfo<?> info = targetClass.newInstance();
                registerExtension(info);
            } catch (MalformedURLException e) {
                throw new ConfigException(MessageFormat
                        .format(Messages.getString("Controller.UNABLE_TO_REGISTER_PLUGIN"), plugin.toString()), e); //$NON-NLS-1$
            } catch (ClassNotFoundException e) {
                throw new ConfigException(MessageFormat
                        .format(Messages.getString("Controller.UNABLE_TO_REGISTER_PLUGIN"), plugin.toString()), e); //$NON-NLS-1$
            } catch (InstantiationException e) {
                throw new ConfigException(MessageFormat
                        .format(Messages.getString("Controller.UNABLE_TO_REGISTER_PLUGIN"), plugin.toString()), e); //$NON-NLS-1$
            } catch (IllegalAccessException e) {
                throw new ConfigException(MessageFormat
                        .format(Messages.getString("Controller.UNABLE_TO_REGISTER_PLUGIN"), plugin.toString()), e); //$NON-NLS-1$
            }
        }
    }

    /**
     * Used to register a extension. Extensions can be sources, stores or actions.
     * @param info The extension information
     */
    @SuppressWarnings("unchecked")
    public void registerExtension(ExtensionInfo<?> info) {
        if (info.getType() == ExtensionType.SOURCE) {
            pluginSources.add((ExtensionInfo<ISource>) info);
        }
        if (info.getType() == ExtensionType.STORE) {
            pluginStores.add((ExtensionInfo<IStore>) info);
        }
        if (info.getType() == ExtensionType.ACTION) {
            pluginActions.add((ExtensionInfo<IAction>) info);
        }
    }

    /**
     * Used to set the addon manager. Mostly used by tests
     *
     * @param xbmcAddonManager The addon manager
     */
    public static void setXBMCAddonManager(XBMCAddonManager xbmcAddonManager) {
        xbmcMgr = xbmcAddonManager;
    }

    /**
     * Used to get the addon manager
     *
     * @return The addon manager
     */
    public XBMCAddonManager getXBMCAddonManager() {
        return xbmcMgr;
    }

    /**
     * Used to convert a media directory location into the media directory object
     * @param mediaDir The location of a media directory
     * @return The media directory
     * @throws ConfigException Thrown if their is a problem reading the configuration
     */
    public MediaDirectory getMediaDirectory(File mediaDir) throws ConfigException {
        MediaDirectory dir = mediaDirs.get(mediaDir);
        if (dir == null) {
            dir = new MediaDirectory(this, configReader, mediaDir);
            mediaDirs.put(mediaDir, dir);
        }
        return dir;
    }

    /**
     * Used to get a list of media directory locations
     * @return Media directory locations
     */
    public Collection<File> getMediaDirectories() {
        return configReader.getMediaDirectories();
    }

    /**
     * Used to get a list of watch directory information
     * @return list of watch directory information
     */
    public Collection<WatchDirConfig> getWatchDirectories() {
        return configReader.getWatchDirectories();
    }

    /**
     * Used to get information about a source
     * @param id The source id
     * @return The extension information
     */
    public ExtensionInfo<? extends ISource> getSourceInfo(String id) {
        for (ExtensionInfo<? extends ISource> sourceInfo : pluginSources) {
            if (sourceInfo.getId().equals(id)) {
                return sourceInfo;
            }
        }
        return null;
    }

    /**
     * Used to get a list of possible sources that can be used with a media directory. This
     * includes any that have been registered via plugins.
     * @return The list of sources.
     */
    public List<ExtensionInfo<? extends ISource>> getAvalibaleSources() {
        List<ExtensionInfo<? extends ISource>> result = new ArrayList<ExtensionInfo<? extends ISource>>();

        for (ExtensionInfo<? extends ISource> info : pluginSources) {
            result.add(info);
        }

        return result;
    }

    /**
     * Used to get a list of possible stores that can be used with a media directory. This
     * includes any that have been registered via plugins.
     * @return The list of sources.
     */
    public List<ExtensionInfo<? extends IStore>> getAvalibaleStores() {
        List<ExtensionInfo<? extends IStore>> result = new ArrayList<ExtensionInfo<? extends IStore>>();

        for (ExtensionInfo<? extends IStore> info : pluginStores) {
            result.add(info);
        }

        return result;
    }

    /**
     * Used to get a list of possible actions that can be used with a media directory. This
     * includes any that have been registered via plugins.
     * @return The list of sources.
     */
    public List<ExtensionInfo<? extends IAction>> getAvalibaleActions() {
        List<ExtensionInfo<? extends IAction>> result = new ArrayList<ExtensionInfo<? extends IAction>>();

        for (ExtensionInfo<? extends IAction> info : pluginActions) {
            result.add(info);
        }

        return result;
    }

    /**
     * Used to get information about a store
     * @param id The store id
     * @return The extension information
     */
    public ExtensionInfo<? extends IStore> getStoreInfo(String id) {
        for (ExtensionInfo<? extends IStore> storeInfo : pluginStores) {
            if (storeInfo.getId().equals(id)) {
                return storeInfo;
            }
        }
        return null;
    }

    /**
     * Used to get information about a action
     * @param id The action id
     * @return The extension information
     */
    public ExtensionInfo<? extends IAction> getActionInfo(String id) {
        for (ExtensionInfo<? extends IAction> actionInfo : pluginActions) {
            if (actionInfo.getId().equals(id)) {
                return actionInfo;
            }
        }
        return null;
    }

    /**
     * Used to find out if test mode is been used. Test mode means that changes are not
     * to be written to disk
     * @return True if test mode, otherwise false
     */
    public boolean isTestRun() {
        return testMode;
    }

    /**
     * Used to find the native folder. Null is returend if it could not be found
     * @return The native folder, or null if not found
     */
    public File getNativeFolder() {
        return configReader.getNativeFolder();
    }

    /**
     * Get the location of the media directory
     * @return The location of the media directory
     * @throws ConfigException Thrown if their is a problem
     */
    public File getConfigDir() throws ConfigException {
        return configReader.getConfigDir();
    }

    /**
     * Used to get the default source information
     * @param mode The mode to look for the source in
     * @return The default source
     * @throws ConfigException Thrown if unable to find a default source
     */
    public ExtensionInfo<? extends ISource> getDefaultSource(Mode mode) throws ConfigException {
        try {
            ExtensionInfo<? extends ISource> info = getSourceInfo(
                    XBMCSource.class.getName() + "#" + xbmcMgr.getDefaultAddonID(mode)); //$NON-NLS-1$
            if (info == null) {
                throw new ConfigException(Messages.getString("Controller.UNABLE_FIND_DEFAULT_SOURCE")); //$NON-NLS-1$
            }
            return info;
        } catch (XBMCException e) {
            throw new ConfigException(Messages.getString("Controller.UNABLE_FIND_DEFAULT_SOURCE"), e); //$NON-NLS-1$
        }
    }

    /**
     * Used to get a list of media directories of a given type
     * @param type The type
     * @return The list of media Directories
     * @throws ConfigException Thrown if their is a problem reading the config
     */
    public List<MediaDirectory> getMediaDirectories(Mode type) throws ConfigException {
        List<MediaDirectory> mediaDirs = new ArrayList<MediaDirectory>();
        for (File mediaDirLoc : getMediaDirectories()) {
            MediaDirectory mediaDir = getMediaDirectory(mediaDirLoc);
            if (mediaDir.getMediaDirConfig().getMode() == type) {
                mediaDirs.add(mediaDir);
            }
        }
        return mediaDirs;
    }

    /**
     * Used to get information on a media file
     * @param file The media file
     * @return The information object. If this is a video file then it will be
     *         of type {@link IVideoFileInfo}.
     * @throws StanwoodException Thrown if their are any problems
     */
    public IMediaFileInfo getMediaFileInformation(File file) throws StanwoodException {
        return fileInfoFetcher.getInformation(file);
    }

    /**
     * Used to get the seen media file database
     * @return The seen media file database
     * @throws ConfigException Thrown if thier is a problem reading the database
     */
    public ISeenDatabase getSeenDB() throws ConfigException {
        if (seenDb == null) {
            try {
                if (configReader.getSeenDatabase() != null
                        && configReader.getSeenDatabase().getResourceId() != null) {
                    DBResource resource = getDatabaseResources()
                            .get(configReader.getSeenDatabase().getResourceId());
                    seenDb = new DatabaseSeenDatabase(resource);
                    migrateSeenEntries();
                } else {
                    seenDb = new FileSeenDatabase(getConfigDir());
                }
                seenDb.read(new NullProgressMonitor());
            } catch (SeenDBException e) {
                throw new ConfigException(Messages.getString("Controller.UNABLE_READ_SEEN_DB"), e); //$NON-NLS-1$
            }
        }
        return seenDb;
    }

    protected void migrateSeenEntries() throws ConfigException, SeenDBException {
        if (((DatabaseSeenDatabase) seenDb).numberOfEntries() == 0) {
            File seenFile = new File(getConfigDir(), "seenFiles.xml"); //$NON-NLS-1$
            if (seenFile.exists()) {
                FileSeenDatabase fileSeenDb = new FileSeenDatabase(getConfigDir());
                fileSeenDb.read(new NullProgressMonitor());
                log.info(Messages.getString("Controller.MigratingFileSeenDBtoDB")); //$NON-NLS-1$
                Collection<SeenEntry> entries = fileSeenDb.getEntries();
                int count = 0;
                for (SeenEntry e : entries) {
                    seenDb.markAsSeen(null, new File(e.getFileName()));
                    count++;
                    if (count % 200 == 0) {
                        log.info(MessageFormat.format(Messages.getString("Controller.MigratedSeenEntries"), count, //$NON-NLS-1$
                                entries.size()));
                    }
                }
                seenDb.write(new NullProgressMonitor());
                log.info(MessageFormat.format(Messages.getString("Controller.MigratedSeenEntries"), count, //$NON-NLS-1$
                        entries.size()));
            }
        }
    }
}