/*
* Jajuk
* Copyright (C) The Jajuk Team
* http://jajuk.info
*
* 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
package org.jajuk.services.startup;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import javax.swing.JOptionPane;
import org.apache.commons.lang.StringUtils;
import org.jajuk.base.Device;
import org.jajuk.base.DeviceManager;
import org.jajuk.base.FileManager;
import org.jajuk.base.SearchResult.SearchResultType;
import org.jajuk.events.JajukEvent;
import org.jajuk.events.JajukEvents;
import org.jajuk.events.ObservationManager;
import org.jajuk.services.core.SessionService;
import org.jajuk.services.dj.Ambience;
import org.jajuk.services.dj.AmbienceManager;
import org.jajuk.services.players.QueueModel;
import org.jajuk.services.webradio.WebRadio;
import org.jajuk.services.webradio.WebRadioManager;
import org.jajuk.ui.widgets.InformationJPanel;
import org.jajuk.util.Conf;
import org.jajuk.util.Const;
import org.jajuk.util.Messages;
import org.jajuk.util.UtilFeatures;
import org.jajuk.util.UtilSystem;
import org.jajuk.util.log.Log;
/**
* Startup facilities for sound engine.
*/
public final class StartupEngineService {
/** List of items to play at startup. */
private static List<org.jajuk.base.File> alToPlay = new ArrayList<org.jajuk.base.File>();
/** File to play. */
private static org.jajuk.base.File fileToPlay;
/** Web radio to play. */
private static WebRadio radio;
/** Index in the queue of the startup file. */
private static int index = -1;
/**
* Instantiates a new startup engine service.
*/
private StartupEngineService() {
// private constructor to hide it from the outside
}
/**
* Launch initial track at startup.
*/
public static void launchInitialTrack() {
try {
String startupMode = Conf.getString(Const.CONF_STARTUP_MODE);
final File fifo = SessionService.getConfFileByPath(Const.FILE_FIFO);
// Restore if required
UtilSystem.recoverFileIfRequired(fifo);
// User explicitly required nothing to start or he left jajuk stopped
boolean doNotStartAnything = Const.STARTUP_MODE_NOTHING.equals(startupMode)
|| Conf.getBoolean(Const.CONF_STARTUP_STOPPED)
// CONF_STARTUP_ITEM is void at first jajuk session and until user launched an item
|| (Const.STARTUP_MODE_ITEM.equals(startupMode) && StringUtils.isBlank(Conf
.getString(Const.CONF_STARTUP_ITEM)))
// Void collection
|| FileManager.getInstance().getElementCount() == 0
// FIFO void or not exists
|| (!fifo.exists() || fifo.length() == 0);
// Populate item to be started and load the stored queue
populateStartupItems();
// Check that the file to play is not null and try to mount its device if required
if (!doNotStartAnything && !isWebradioStartup()) {
checkFileToPlay();
}
// Set the index of the file to play within the queue.
// We still need to compute index of file to play even if we play nothing or a radio
// because user may do a "play" and the next file index must be ready.
// However, we don't need to test file availability with checkFileToPlay() method.
updateIndex();
// Push the new queue
boolean bRepeat = Conf.getBoolean(Const.CONF_STATE_REPEAT_ALL);
QueueModel.insert(UtilFeatures.createStackItems(alToPlay, bRepeat, false), 0);
// Force queue index because insert increase it so it would be set to queue size after the insert
QueueModel.setIndex(index);
// Start the file or the radio
// If user leaved jajuk in stopped mode, do nothing
if (!doNotStartAnything && isWebradioStartup() && radio != null) {
launchRadio();
} else if (!doNotStartAnything && fileToPlay != null) {
launchFile();
}
} catch (Exception e) {
Log.error(e);
Messages.getChoice(Messages.getErrorMessage(23), JOptionPane.DEFAULT_OPTION,
JOptionPane.WARNING_MESSAGE);
}
}
/**
* Launch fileToPlay.
*/
private static void launchFile() {
new Thread("Track Startup Thread") {
@Override
public void run() {
QueueModel.goTo(index);
}
}.start();
}
/**
* Return whether a webradio startup is required (it may be null if the webradio is unknown by the collection).
*
* @return whether a webradio startup is required
*/
private static boolean isWebradioStartup() {
String startupMode = Conf.getString(Const.CONF_STARTUP_MODE);
if (Conf.getBoolean(Const.CONF_WEBRADIO_WAS_PLAYING)
&& (Const.STARTUP_MODE_LAST_KEEP_POS.equals(startupMode) || Const.STARTUP_MODE_LAST
.equals(startupMode))) {
return true;
}
if (Const.STARTUP_MODE_ITEM.equals(startupMode)) {
String conf = Conf.getString(Const.CONF_STARTUP_ITEM);
if (conf.matches(SearchResultType.WEBRADIO.name() + ".*")) {
return true;
}
}
return false;
}
/**
* Restore the queue we got at last session exit.
*/
private static void restoreQueue() {
final File fifo = SessionService.getConfFileByPath(Const.FILE_FIFO);
try {
UtilSystem.recoverFileIfRequired(fifo);
} catch (IOException e) {
Log.error(e);
}
if (!fifo.exists()) {
Log.debug("No fifo file");
} else {
try {
final BufferedReader br = new BufferedReader(new FileReader(
SessionService.getConfFileByPath(Const.FILE_FIFO)));
try {
String s = null;
for (;;) {
s = br.readLine();
if (s == null) {
break;
}
final org.jajuk.base.File file = FileManager.getInstance().getFileByID(s);
if ((file != null) && file.isReady()) {
alToPlay.add(file);
}
}
} finally {
br.close();
}
} catch (final IOException ioe) {
Log.error(ioe);
}
}
}
/**
* Find item to play and build the queue.
*/
private static void populateStartupItems() {
String startupMode = Conf.getString(Const.CONF_STARTUP_MODE);
Ambience ambience = AmbienceManager.getInstance().getSelectedAmbience();
// an item (track or radio) has been forced by user
if (Const.STARTUP_MODE_ITEM.equals(startupMode)) {
String conf = Conf.getString(Const.CONF_STARTUP_ITEM);
String item = conf.substring(conf.indexOf('/') + 1, conf.length());
if (conf.matches(SearchResultType.FILE.name() + ".*")) {
fileToPlay = FileManager.getInstance().getFileByID(item);
if (fileToPlay == null) {
Log.warn("Unknown startup file : " + fileToPlay.getAbsolutePath());
}
} else if (conf.matches(SearchResultType.WEBRADIO.name() + ".*")) {
radio = WebRadioManager.getInstance().getWebRadioByName(item);
if (radio == null) {
Log.warn("Unknown startup webradio : " + radio.getName());
}
}
}
// We play last item
else if (Const.STARTUP_MODE_LAST.equals(startupMode)
|| Const.STARTUP_MODE_LAST_KEEP_POS.equals(startupMode)) {
//Restore the queue in these cases
restoreQueue();
// If we were playing a webradio when leaving, launch it
if (Conf.getBoolean(Const.CONF_WEBRADIO_WAS_PLAYING)) {
radio = WebRadioManager.getInstance().getWebRadioByName(
Conf.getString(Const.CONF_DEFAULT_WEB_RADIO));
}
// last file from beginning or last file keep position
else {
index = Conf.getInt(Const.CONF_STARTUP_QUEUE_INDEX);
if (index >= 0 && index < alToPlay.size()) {
fileToPlay = alToPlay.get(index);
}
}
} else if (Const.STARTUP_MODE_NOTHING.equals(startupMode)) {
//Restore the queue in these cases
restoreQueue();
}
// Shuffle mode
else if (Conf.getString(Const.CONF_STARTUP_MODE).equals(Const.STARTUP_MODE_SHUFFLE)) {
// Filter files by ambience or if none ambience matches, perform a global shuffle
// ignoring current ambience
alToPlay = UtilFeatures.filterByAmbience(
FileManager.getInstance().getGlobalShufflePlaylist(), ambience);
if (alToPlay.size() == 0) {
alToPlay = FileManager.getInstance().getGlobalShufflePlaylist();
}
if (alToPlay != null && alToPlay.size() > 0) {
fileToPlay = alToPlay.get(0);
}
// Best of mode
} else if (Conf.getString(Const.CONF_STARTUP_MODE).equals(Const.STARTUP_MODE_BESTOF)) {
// Filter files by ambience or if none ambience matches, perform a global best-of selection
// ignoring current ambience
alToPlay = UtilFeatures.filterByAmbience(FileManager.getInstance().getGlobalBestofPlaylist(),
ambience);
if (alToPlay.size() == 0) {
alToPlay = FileManager.getInstance().getGlobalBestofPlaylist();
}
if (alToPlay != null && alToPlay.size() > 0) {
fileToPlay = alToPlay.get(0);
}
// Novelties mode
} else if (Conf.getString(Const.CONF_STARTUP_MODE).equals(Const.STARTUP_MODE_NOVELTIES)) {
// Filter files by ambience or if none ambience matches, perform a global novelties selection
// ignoring current ambience
alToPlay = UtilFeatures.filterByAmbience(FileManager.getInstance()
.getGlobalNoveltiesPlaylist(), ambience);
if (alToPlay.size() == 0) {
alToPlay = FileManager.getInstance().getGlobalNoveltiesPlaylist();
}
if (alToPlay != null && alToPlay.size() > 0) {
// shuffle the selection
Collections.shuffle(alToPlay, UtilSystem.getRandom());
fileToPlay = alToPlay.get(0);
} else {
// Alert user that no novelties have been found
InformationJPanel.getInstance().setMessage(Messages.getString("Error.127"),
InformationJPanel.MessageType.ERROR);
}
}
// If the queue was empty and a file to play is provided, build a new queue
// with this track alone
if (alToPlay.size() == 0 && fileToPlay != null) {
alToPlay.add(fileToPlay);
}
}
/**
* Check that the file can actually be played and handle errors if it's not the case.
*
* @return whether the file can actually be played
*/
private static boolean checkFileToPlay() {
boolean result = true;
// Try to mount the file to play
if (fileToPlay != null) {
if (!fileToPlay.isReady()) {
// File exists but is not mounted, just notify the error
// without annoying dialog at each startup try to mount
// device
Log.debug("Startup file located on an unmounted device" + ", try to mount it");
try {
fileToPlay.getDevice().mount(false);
Log.debug("Mount OK");
} catch (final Exception e) {
Log.debug("Mount failed");
final Properties pDetail = new Properties();
pDetail.put(Const.DETAIL_CONTENT, fileToPlay);
pDetail.put(Const.DETAIL_REASON, "10");
ObservationManager.notify(new JajukEvent(JajukEvents.PLAY_ERROR, pDetail));
result = false;
}
}
} else {
// file no more exists
Messages.getChoice(Messages.getErrorMessage(23), JOptionPane.DEFAULT_OPTION,
JOptionPane.WARNING_MESSAGE);
// no more first file, we ignore any stored fifo as it may contains
// others disappeared files
result = false;
}
return result;
}
/**
* Set index of fileToPlay among alToPlay list.
*/
private static void updateIndex() {
// keep index from configuration if already set
if (index != -1) {
return;
}
// fileToPlay is null if nothing has to be played, then we keep default index value (0)
if (fileToPlay != null) {
// find the index of last played track
index = -1;
for (int i = 0; i < alToPlay.size(); i++) {
if (fileToPlay.getID().equals(alToPlay.get(i).getID())) {
index = i;
break;
}
}
if (index == -1) {
// Track not stored, push it first
alToPlay.add(0, fileToPlay);
index = 0;
}
}
}
/**
* Launch the startup webradio.
*/
private static void launchRadio() {
new Thread("WebRadio launch thread") {
@Override
public void run() {
QueueModel.launchRadio(radio);
}
}.start();
}
/**
* Auto-Mount required devices.
*/
public static void autoMount() {
for (final Device device : DeviceManager.getInstance().getDevices()) {
if (device.getBooleanValue(Const.XML_DEVICE_AUTO_MOUNT)) {
try {
device.mount(false);
} catch (final Exception e) {
Log.error(112, device.getName(), e);
continue;
}
}
}
}
}