/*
* 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.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystems;
import java.nio.file.Paths;
import java.text.Normalizer;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.musicmount.io.Resource;
import org.musicmount.io.ResourceProvider;
import org.musicmount.io.file.FileResourceProvider;
import org.musicmount.io.server.dav.DAVResourceProvider;
import org.musicmount.io.server.smb.SMBResourceProvider;
import org.musicmount.util.LoggingUtil;
public class MusicMountBuildCommand {
static final Logger LOGGER = Logger.getLogger(MusicMountBuildCommand.class.getName());
static final Map<String, String> userProvidedPasswords = new HashMap<>();
static URI getServerURI(URI uri) throws URISyntaxException {
if (uri.getUserInfo() != null && uri.getUserInfo().indexOf(":") < 0) {
String password = null;
if (userProvidedPasswords.containsKey(uri.getAuthority())) {
password = userProvidedPasswords.get(uri.getAuthority());
} else {
char[] passwordChars = System.console().readPassword("%s's password:", uri.getAuthority());
if (passwordChars != null && passwordChars.length > 0) {
password = String.valueOf(passwordChars);
}
userProvidedPasswords.put(uri.getAuthority(), password);
}
if (password != null) {
uri = new URI(uri.getScheme(), uri.getUserInfo() + ":" + password, uri.getHost(), uri.getPort(), uri.getPath(), null, null);
}
}
return uri;
}
static ResourceProvider getResourceProvider(String string) throws IOException, URISyntaxException {
String uriString = string.trim();
/*
* convert file separators to URI slashes
*/
uriString = uriString.replace(FileSystems.getDefault().getSeparator(), "/");
uriString = uriString.replace(" ", "%20"); // TODO URI encoding
/*
* make sure base ends with '/'and is absolute
*/
if (!uriString.endsWith("/")) {
uriString += "/";
}
/*
* create URI and switch by scheme...
*/
URI uri = new URI(uriString);
if (uri.isAbsolute() && uri.getScheme().length() > 1) { // beware windows drive letters, e.g. C:\foo
switch (uri.getScheme()) {
case "file":
return new FileResourceProvider(Paths.get(uri).toString());
case "http":
case "https":
return new DAVResourceProvider(getServerURI(uri));
case "smb":
return new SMBResourceProvider(getServerURI(uri));
default:
throw new IOException("unsupported scheme: " + uri.getScheme());
}
}
/*
* assume simple file path
*/
return new FileResourceProvider(string);
}
/**
* Resolve base/path to resource.
* @param base base resource (URL or file path, may be <code>null</code>)
* @param path resource path, resolved relative to base.
* @return resource
* @throws IOException IO exception
* @throws URISyntaxException URI syntax exception
*/
public static Resource getResource(String base, String path) throws IOException, URISyntaxException {
if (base == null) {
return getResourceProvider(path).getBaseDirectory();
}
/*
* absolute base directory
*/
Resource baseDirectory = getResourceProvider(base).getBaseDirectory();
/*
* replace file separator
*/
String fileSeparator = baseDirectory.getPath().getFileSystem().getSeparator();
path = path.replace(FileSystems.getDefault().getSeparator(), fileSeparator);
/*
* make path relative + directory
*/
while (path.startsWith(fileSeparator)) {
path = path.substring(1);
}
if (!path.endsWith(fileSeparator)) {
path += fileSeparator;
}
/*
* resolve (i.e. append) path and normalize
*/
return baseDirectory.getProvider().newResource(baseDirectory.getPath().resolve(path).normalize());
}
static void exitWithError(String command, String error) {
System.err.println();
System.err.println("*** " + (error == null ? "internal error" : error));
System.err.println();
System.err.println(String.format("Usage: %s [options] <musicFolder> <mountFolder>", command));
System.err.println();
System.err.println("Generate static MusicMount site from music in <musicFolder> into <mountFolder>");
System.err.println();
System.err.println(" <music_folder> input folder (containing the music library)");
System.err.println(" <mount_folder> output folder (to contain the generated site)");
System.err.println();
System.err.println("Folders may be local directory paths or smb|http|https URLs, e.g. smb://user:pass@host/path/");
System.err.println();
System.err.println("Options:");
System.err.println(" --music <path> music path, default is relative path from <mountFolder> to <musicFolder>");
System.err.println(" --base <folder> base folder, <musicFolder> and <mountFolder> are relative to this folder");
System.err.println(" --retina double image resolution");
System.err.println(" --grouping use grouping tag to group album tracks");
System.err.println(" --unknownGenre report missing genre as 'Unknown'");
System.err.println(" --noTrackIndex do not generate a track index");
System.err.println(" --noVariousArtists exclude 'Various Artists' from album artist index");
System.err.println(" --directoryIndex use 'path/' instead of 'path/index.ext'");
System.err.println(" --full full parse, don't use asset store");
System.err.println(" --pretty pretty-print JSON documents");
System.err.println(" --verbose more detailed console output");
// System.err.println(" --normalize <form> normalize asset paths, 'NFC'|'NFD' (experimental)");
// System.err.println(" --noImages do not generate images");
// System.err.println(" --xml generate XML instead of JSON");
System.err.close();
System.exit(1);
}
/**
* Build a MusicMount site.
* @param command command name (e.g. "build")
* @param args options, inputFolder, outputFolder
* @throws Exception something went wrong...
*/
public static void execute(String command, String... args) throws Exception {
MusicMountBuilder builder = new MusicMountBuilder();
String optionBase = null;
String optionMusic = null;
boolean optionVerbose = false;
int optionsLength = 0;
boolean optionsDone = false;
while (optionsLength < args.length && !optionsDone) {
switch (args[optionsLength]) {
case "--music":
if (++optionsLength == args.length) {
exitWithError(command, "invalid arguments");
}
optionMusic = args[optionsLength];
break;
case "--base":
if (++optionsLength == args.length) {
exitWithError(command, "invalid arguments");
}
optionBase = args[optionsLength];
break;
case "--retina":
builder.getConfig().setRetina(true);
break;
case "--pretty":
builder.getConfig().setPretty(true);
break;
case "--full":
builder.getConfig().setFull(true);
break;
case "--verbose":
optionVerbose = true;
break;
case "--unknownGenre":
builder.getConfig().setUnknownGenre(true);
break;
case "--noTrackIndex":
builder.getConfig().setNoTrackIndex(true);
break;
case "--noVariousArtists":
builder.getConfig().setNoVariousArtists(true);
break;
case "--noImages":
builder.getConfig().setNoImages(true);
break;
case "--noDirectoryIndex": // deprecated
break;
case "--directoryIndex":
builder.getConfig().setDirectoryIndex(true);
break;
case "--xml":
builder.getConfig().setXml(true);
break;
case "--grouping":
builder.getConfig().setGrouping(true);
break;
case "--normalize":
if (++optionsLength == args.length) {
exitWithError(command, "invalid arguments");
}
try {
builder.getConfig().setNormalizer(Normalizer.Form.valueOf(args[optionsLength]));
} catch (IllegalArgumentException e) {
exitWithError(command, "invalid normalize form: " + args[optionsLength]);
}
break;
default:
if (args[optionsLength].startsWith("-")) {
exitWithError(command, "unknown option: " + args[optionsLength]);
} else {
optionsDone = true;
}
}
if (!optionsDone) {
optionsLength++;
}
}
for (int i = optionsLength; i < args.length; i++) {
if (args[i].startsWith("-")) {
exitWithError(command, "invalid arguments");
}
}
Resource musicFolder = null;
Resource mountFolder = null;
switch (args.length - optionsLength) {
case 0:
case 1:
exitWithError(command, "missing arguments");
break;
case 2:
musicFolder = getResource(optionBase, args[optionsLength]);
mountFolder = getResource(optionBase, args[optionsLength + 1]);
if (optionMusic == null && musicFolder.getPath().getRoot().equals(mountFolder.getPath().getRoot())) {
try { // calculate relative path from mountFolder to musicFolder
optionMusic = mountFolder.getPath().relativize(musicFolder.getPath()).toString();
} catch (IllegalArgumentException e) {
// cannot relativize
}
}
if (optionMusic == null) {
exitWithError(command, "could not calculate music path as relative path from <mountFolder> to <musicFolder>, use --music <path>");
}
optionMusic = optionMusic.replace(FileSystems.getDefault().getSeparator(), "/");
break;
default:
exitWithError(command, "bad arguments");
}
try {
if (mountFolder.exists()) {
if (!mountFolder.isDirectory()) {
exitWithError(command, "mount folder is not a directory: " + mountFolder);
}
} else {
exitWithError(command, "mount folder doesn't exist: " + mountFolder);
}
} catch (IOException e) {
exitWithError(command, "cannot connect to mount folder \"" + mountFolder.getPath().toUri() + "\": " + e.getMessage());
}
try {
if (musicFolder.exists()) {
if (!musicFolder.isDirectory()) {
exitWithError(command, "music folder is not a directory: " + musicFolder);
}
} else {
exitWithError(command, "music folder doesn't exist: " + musicFolder);
}
} catch (IOException e) {
exitWithError(command, "cannot connect to music folder \"" + musicFolder.getPath().toUri() + "\": " + e.getMessage());
}
/**
* Configure logging
*/
LoggingUtil.configure(MusicMountBuildCommand.class.getPackage().getName(), optionVerbose ? Level.FINER : Level.FINE);
if (!"file".equals(musicFolder.getPath().toUri().getScheme()) || !"file".equals(mountFolder.getPath().toUri().getScheme())) {
LOGGER.warning("Remote file system support is experimental/alpha!");
}
/**
* Run builder
*/
builder.build(musicFolder, mountFolder, optionMusic);
}
public static void main(String[] args) throws Exception {
execute(MusicMountBuildCommand.class.getSimpleName(), args);
}
}