/* * Copyright 2013-2014 Odysseus Software GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.musicmount.builder; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import org.musicmount.builder.impl.AssetLocator; import org.musicmount.builder.impl.AssetStore; import org.musicmount.builder.impl.ImageFormatter; import org.musicmount.builder.impl.LibraryParser; import org.musicmount.builder.impl.LocalStrings; import org.musicmount.builder.impl.ResourceLocator; import org.musicmount.builder.impl.AssetStoreRepository; import org.musicmount.builder.impl.ResponseFormatter; import org.musicmount.builder.impl.SimpleAssetLocator; import org.musicmount.builder.impl.SimpleAssetParser; import org.musicmount.builder.impl.SimpleResourceLocator; import org.musicmount.builder.model.Album; import org.musicmount.builder.model.Artist; import org.musicmount.builder.model.ArtistType; import org.musicmount.builder.model.Library; import org.musicmount.io.Resource; import org.musicmount.util.LoggingProgressHandler; import org.musicmount.util.ProgressHandler; import org.musicmount.util.VersionUtil; public class MusicMountBuilder { static final Logger LOGGER = Logger.getLogger(MusicMountBuilder.class.getName()); /** * prevent ImageIO from using file cache */ static { ImageIO.setUseCache(false); // TODO not sure if this is really useful... } /** * API version string */ static final String API_VERSION = VersionUtil.getSpecificationVersion(); /** * Name of asset store file. */ static final String ASSET_STORE = ".musicmount.gz"; private final MusicMountBuildConfig config; private ProgressHandler progressHandler = new LoggingProgressHandler(LOGGER, Level.FINE); private final int maxAssetThreads; private final int maxImageThreads; public MusicMountBuilder() { this(new MusicMountBuildConfig()); } public MusicMountBuilder(MusicMountBuildConfig config) { this(config, 1, Integer.MAX_VALUE); } public MusicMountBuilder(MusicMountBuildConfig config, int maxAssetThreads, int maxImageThreads) { this.config = config; this.maxAssetThreads = maxAssetThreads; this.maxImageThreads = maxImageThreads; } public MusicMountBuildConfig getConfig() { return config; } public ProgressHandler getProgressHandler() { return progressHandler; } public void setProgressHandler(ProgressHandler progressHandler) { this.progressHandler = progressHandler; } private OutputStream createOutputStream(Resource file) throws IOException { if (!file.getParent().exists()) { // file.getParent() may be a symbolic link target (mount folder) file.getParent().mkdirs(); } return new BufferedOutputStream(file.getOutputStream()); } public void build(Resource musicFolder, Resource mountFolder, String musicPath) throws Exception { LOGGER.info("Starting Build..."); LOGGER.info("Music folder: " + musicFolder.getPath()); LOGGER.info("Mount folder: " + mountFolder.getPath()); LOGGER.info("Music path : " + musicPath); AssetStore assetStore = new AssetStore(API_VERSION, musicFolder); Resource siteAssetStoreFile = mountFolder.resolve(ASSET_STORE); boolean siteAssetStoreLoaded = false; if (!config.isFull()) { try { if (siteAssetStoreFile.exists()) { assetStore.load(siteAssetStoreFile, progressHandler); siteAssetStoreLoaded = true; } else { // check user's asset store repository if it has an asset store to load from (e.g. from "live" command) } } catch (Exception e) { LOGGER.log(Level.WARNING, "Failed to load site asset store", e); assetStore = new AssetStore(API_VERSION, musicFolder); } if (!siteAssetStoreLoaded) { // check user's asset store repository if it has an asset store to load from (e.g. from "live" command) Resource userAssetStoreFile = AssetStoreRepository.getAssetStoreResource(AssetStoreRepository.getUserAssetStoreRepository(), musicFolder); if (userAssetStoreFile != null) { try { if (userAssetStoreFile.exists()) { LOGGER.fine("User asset store found"); assetStore.load(userAssetStoreFile, progressHandler); } } catch (Exception e) { LOGGER.log(Level.WARNING, "Failed to load user asset store", e); assetStore = new AssetStore(API_VERSION, musicFolder); } } } } assetStore.update(new SimpleAssetParser(), maxAssetThreads, progressHandler); if (progressHandler != null) { progressHandler.beginTask(-1, "Building music libary..."); } Library library = new LibraryParser(config.isGrouping()).parse(assetStore.assets()); if (config.isNoVariousArtists()) { // remove "various artists" album artist (hack) library.getAlbumArtists().remove(null); } Set<Album> changedAlbums = assetStore.sync(library.getAlbums()); if (siteAssetStoreLoaded) { LOGGER.fine(String.format("Number of albums changed: %d", changedAlbums.size())); } if (progressHandler != null) { progressHandler.endTask(); } if (config.isNoImages()) { assetStore.setRetina(null); } else { ImageFormatter formatter = new ImageFormatter(new SimpleAssetParser(), config.isRetina()); final boolean retinaChange = !Boolean.valueOf(config.isRetina()).equals(assetStore.getRetina()); if (LOGGER.isLoggable(Level.FINE) && retinaChange && siteAssetStoreLoaded) { LOGGER.fine(String.format("Retina state %s", assetStore.getRetina() == null ? "unknown" : "changed")); } ResourceLocator resourceLocator = new SimpleResourceLocator(mountFolder, config.isXml(), config.isNoImages(), config.isNoTrackIndex()); Set<Album> imageAlbums = retinaChange || config.isFull() ? new HashSet<>(library.getAlbums()) : changedAlbums; formatter.formatImages(library, resourceLocator, imageAlbums, maxImageThreads, progressHandler); assetStore.setRetina(config.isRetina()); } generateResponseFiles(library, musicFolder, mountFolder, musicPath); if (!siteAssetStoreLoaded || changedAlbums.size() > 0) { try { assetStore.save(siteAssetStoreFile, progressHandler); } catch (Exception e) { LOGGER.log(Level.WARNING, "Failed to save asset store", e); } } LOGGER.info("Done."); } void generateResponseFiles(Library library, Resource musicFolder, Resource mountFolder, String musicPath) throws Exception { if (progressHandler != null) { progressHandler.beginTask(config.isNoTrackIndex() ? 3 : 4, "Generating JSON..."); } LocalStrings localStrings = new LocalStrings(Locale.ENGLISH); ResponseFormatter<?> formatter; if (config.isXml()) { formatter = new ResponseFormatter.XML(API_VERSION, localStrings, config.isDirectoryIndex(), config.isUnknownGenre(), config.isGrouping(), config.isPretty()); } else { formatter = new ResponseFormatter.JSON(API_VERSION, localStrings, config.isDirectoryIndex(), config.isUnknownGenre(), config.isGrouping(), config.isPretty()); } AssetLocator assetLocator = new SimpleAssetLocator(musicFolder, musicPath, config.getNormalizer()); ResourceLocator resourceLocator = new SimpleResourceLocator(mountFolder, config.isXml(), config.isNoImages(), config.isNoTrackIndex()); int workDone = -1; /* * album artists */ Map<Artist, Album> representativeAlbums = new HashMap<Artist, Album>(); for (Artist artist : library.getAlbumArtists().values()) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Generating album collection for album artist: " + artist.getTitle()); } try (OutputStream output = createOutputStream(resourceLocator.getResource(resourceLocator.getAlbumCollectionPath(artist)))) { representativeAlbums.put(artist, formatter.formatAlbumCollection(artist, output, resourceLocator)); } } try (OutputStream output = createOutputStream(resourceLocator.getResource(resourceLocator.getArtistIndexPath(ArtistType.AlbumArtist)))) { formatter.formatArtistIndex(library.getAlbumArtists().values(), ArtistType.AlbumArtist, output, resourceLocator, representativeAlbums); } if (progressHandler != null) { progressHandler.progress(++workDone, String.format("%5d album artists", library.getAlbumArtists().size())); } /* * artists */ representativeAlbums.clear(); for (Artist artist : library.getTrackArtists().values()) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Generating album collection for artist: " + artist.getTitle()); } try (OutputStream output = createOutputStream(resourceLocator.getResource(resourceLocator.getAlbumCollectionPath(artist)))) { representativeAlbums.put(artist, formatter.formatAlbumCollection(artist, output, resourceLocator)); } } try (OutputStream output = createOutputStream(resourceLocator.getResource(resourceLocator.getArtistIndexPath(ArtistType.TrackArtist)))) { formatter.formatArtistIndex(library.getTrackArtists().values(), ArtistType.TrackArtist, output, resourceLocator, representativeAlbums); } if (progressHandler != null) { progressHandler.progress(++workDone, String.format("%5d artists", library.getTrackArtists().size())); } /* * albums */ for (Album album : library.getAlbums()) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Generating album: " + album.getTitle()); } try (OutputStream output = createOutputStream(resourceLocator.getResource(resourceLocator.getAlbumPath(album)))) { formatter.formatAlbum(album, output, resourceLocator, assetLocator); } } try (OutputStream output = createOutputStream(resourceLocator.getResource(resourceLocator.getAlbumIndexPath()))) { formatter.formatAlbumIndex(library.getAlbums(), output, resourceLocator); } if (progressHandler != null) { progressHandler.progress(++workDone, String.format("%5d albums", library.getAlbums().size())); } /* * track index */ if (!config.isNoTrackIndex()) { try (OutputStream output = createOutputStream(resourceLocator.getResource(resourceLocator.getTrackIndexPath()))) { formatter.formatTrackIndex(library.getTracks(), output, resourceLocator, null); } if (progressHandler != null) { progressHandler.progress(++workDone, String.format("%5d tracks", library.getTracks().size())); } } /* * service index last */ try (OutputStream output = createOutputStream(resourceLocator.getResource(resourceLocator.getServiceIndexPath()))) { formatter.formatServiceIndex(resourceLocator, output); } if (progressHandler != null) { progressHandler.endTask(); } } }