/*
* 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.base;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.lang.StringUtils;
import org.jajuk.services.core.SessionService;
import org.jajuk.util.Conf;
import org.jajuk.util.Const;
import org.jajuk.util.Messages;
import org.jajuk.util.ReadOnlyIterator;
import org.jajuk.util.UpgradeManager;
import org.jajuk.util.UtilString;
import org.jajuk.util.UtilSystem;
import org.jajuk.util.error.JajukException;
import org.jajuk.util.log.Log;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Items root container.
*/
public final class Collection extends DefaultHandler {
/** The Constant TAG_CLOSE_NEWLINE. */
private static final String TAG_CLOSE_NEWLINE = ">\n";
/** The Constant TAB_CLOSE_TAG_START. */
private static final String TAB_CLOSE_TAG_START = "</";
/** Self instance. */
private static Collection coll = new Collection();
private static long lTime;
/** Current ItemManager manager. */
private ItemManager manager;
/** upgrade for track IDs. */
private final Map<String, String> hmWrongRightTrackID = new HashMap<String, String>();
/** upgrade for album IDs. */
private final Map<String, String> hmWrongRightAlbumID = new HashMap<String, String>();
/** upgrade for artist IDs. */
private final Map<String, String> hmWrongRightArtistID = new HashMap<String, String>();
/** upgrade for album-artists IDs. */
private final Map<String, String> hmWrongRightAlbumArtistID = new HashMap<String, String>();
/** upgrade for genre IDs. */
private final Map<String, String> hmWrongRightGenreID = new HashMap<String, String>();
/** upgrade for device IDs. */
private final Map<String, String> hmWrongRightDeviceID = new HashMap<String, String>();
/** upgrade for directory IDs. */
private final Map<String, String> hmWrongRightDirectoryID = new HashMap<String, String>();
/** upgrade for file IDs. */
private final Map<String, String> hmWrongRightFileID = new HashMap<String, String>();
/** upgrade for playlist IDs. */
private final Map<String, String> hmWrongRightPlaylistFileID = new HashMap<String, String>();
/** Conversion of types from Jajuk < 1.4 */
private final static Map<String, String> CONVERSION;
static {
CONVERSION = new HashMap<String, String>(12);
CONVERSION.put("0", "mp3");
CONVERSION.put("1", "m3u");
CONVERSION.put("2", "ogg");
CONVERSION.put("3", "wav");
CONVERSION.put("4", "au");
CONVERSION.put("5", "flac");
CONVERSION.put("6", "wma");
CONVERSION.put("7", "aac");
CONVERSION.put("8", "m4a");
CONVERSION.put("9", "ram");
CONVERSION.put("10", "mp2");
}
/** [Perf] flag used to accelerate conversion. */
private boolean needCheckConversions = true;
/** [PERF] Does the type has been checked once for ID computation change ? Indeed, we check only one element of each type to check if this computation changed for perfs. */
private boolean needCheckID = false;
// Constants value, use lower value for mist numerous items to parse
/**
* .
*/
private enum Stage {
STAGE_NONE,
/** The Constant STAGE_FILES. */
STAGE_FILES,
/** The Constant STAGE_DIRECTORIES. */
STAGE_DIRECTORIES,
/** The Constant STAGE_TRACKS. */
STAGE_TRACKS,
/** The Constant STAGE_ALBUMS. */
STAGE_ALBUMS,
/** The Constant STAGE_ARTISTS. */
STAGE_ARTISTS,
/** The Constant STAGE_GENRES. */
STAGE_GENRES,
/** The Constant STAGE_PLAYLIST_FILES. */
STAGE_PLAYLIST_FILES,
/** The Constant STAGE_PLAYLISTS. */
STAGE_PLAYLISTS,
/** The Constant STAGE_TYPES. */
STAGE_TYPES,
/** The Constant STAGE_DEVICES. */
STAGE_DEVICES,
/** The Constant STAGE_YEARS. */
STAGE_YEARS,
/** STAGE_ALBUM_ARTIST. */
STAGE_ALBUM_ARTIST
}
/** *************************************************************************** [PERF] provide current stage (files, tracks...) used to optimize switch when parsing the collection ************************************************************************** */
private Stage stage = Stage.STAGE_NONE;
/** The Constant additionFormatter. */
private final DateFormat additionFormatter = UtilString.getAdditionDateFormatter();
/**
* Instance getter.
*
* @return the instance
*/
public static Collection getInstance() {
return coll;
}
/**
* Hidden constructor.
*/
private Collection() {
super();
}
/**
* Write current collection to collection file for persistence between
* sessions.
*
* @param collectionFile
*
* @throws IOException Signals that an I/O exception has occurred.
*/
public static synchronized void commit() throws IOException {
long time = System.currentTimeMillis();
String sCharset = Conf.getString(Const.CONF_COLLECTION_CHARSET);
java.io.File out = SessionService.getConfFileByPath(Const.FILE_COLLECTION + "."
+ Const.FILE_SAVING_FILE_EXTENSION);
final BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(out),
sCharset), 1000000);
try {
bw.write("<?xml version='1.0' encoding='" + sCharset + "'?>\n");
bw.write("<" + Const.XML_COLLECTION + " " + Const.XML_VERSION + "='" + Const.JAJUK_VERSION
+ "'>\n");
// Devices
writeItemList(bw, DeviceManager.getInstance().toXML(), DeviceManager.getInstance()
.getDevices(), DeviceManager.getInstance().getXMLTag(), 40);
// Genres
writeItemList(bw, GenreManager.getInstance().toXML(), GenreManager.getInstance().getGenres(),
GenreManager.getInstance().getXMLTag(), 40);
// Artists
writeItemList(bw, ArtistManager.getInstance().toXML(), ArtistManager.getInstance()
.getArtists(), ArtistManager.getInstance().getXMLTag(), 40);
// Album artists
writeItemList(bw, AlbumArtistManager.getInstance().toXML(), AlbumArtistManager.getInstance()
.getAlbumArtists(), AlbumArtistManager.getInstance().getXMLTag(), 40);
// Albums
writeItemList(bw, AlbumManager.getInstance().toXML(), AlbumManager.getInstance().getAlbums(),
AlbumManager.getInstance().getXMLTag(), 40);
// Years
writeItemList(bw, YearManager.getInstance().toXML(), YearManager.getInstance().getYears(),
YearManager.getInstance().getXMLTag(), 40);
// Tracks
// Cannot use writeItemList() method as we have a bit of special handling inside the loop here
TrackManager.getInstance().getLock().readLock().lock();
try {
ReadOnlyIterator<Track> tracks = TrackManager.getInstance().getTracksIterator();
bw.write(TrackManager.getInstance().toXML());
while (tracks.hasNext()) {
Track track = tracks.next();
// We clean up all orphan tracks
if (track.getFiles().size() > 0) {
bw.write(track.toXml());
}
}
} finally {
TrackManager.getInstance().getLock().readLock().unlock();
}
writeString(bw, TrackManager.getInstance().getXMLTag(), 200);
// Directories
writeItemList(bw, DirectoryManager.getInstance().toXML(), DirectoryManager.getInstance()
.getDirectories(), DirectoryManager.getInstance().getXMLTag(), 100);
// Files
writeItemList(bw, FileManager.getInstance().toXML(), FileManager.getInstance().getFiles(),
FileManager.getInstance().getXMLTag(), 200);
// Playlists
writeItemList(bw, PlaylistManager.getInstance().toXML(), PlaylistManager.getInstance()
.getPlaylists(), PlaylistManager.getInstance().getXMLTag(), 200);
// end of collection
bw.write("</" + Const.XML_COLLECTION + TAG_CLOSE_NEWLINE);
bw.flush();
} finally {
bw.close();
}
// Override initial file
java.io.File finalFile = SessionService.getConfFileByPath(Const.FILE_COLLECTION);
UtilSystem.saveFileWithRecoverySupport(finalFile);
Log.debug("Collection commited in " + (System.currentTimeMillis() - time) + " ms");
}
/**
* Write item list.
*
* @param bw
* @param header
* @param items
* @param footer
* @param buffer
*
* @throws IOException Signals that an I/O exception has occurred.
*/
private static void writeItemList(BufferedWriter bw, String header, List<? extends Item> items,
String footer, int buffer) throws IOException {
bw.write(header);
for (Item item : items) {
bw.write(item.toXml());
}
writeString(bw, footer, buffer);
}
/**
* Write string.
*
* @param bw
* @param toWrite
* @param buffer
*
* @throws IOException Signals that an I/O exception has occurred.
*/
private static void writeString(BufferedWriter bw, String toWrite, int buffer) throws IOException {
StringBuilder sb = new StringBuilder(buffer);
sb.append(TAB_CLOSE_TAG_START);
sb.append(toWrite);
sb.append(TAG_CLOSE_NEWLINE);
bw.write(sb.toString());
}
/**
* Parse collection.xml file and put all collection information into memory
*
* @param file
*
* @throws SAXException the SAX exception
* @throws ParserConfigurationException the parser configuration exception
* @throws JajukException the jajuk exception
* @throws IOException Signals that an I/O exception has occurred.
*/
public static void load(File file) throws SAXException, ParserConfigurationException,
JajukException, IOException {
// If we load the regular collection.xml file, try to recover it from previous crash
java.io.File regularFile = SessionService.getConfFileByPath(Const.FILE_COLLECTION);
if (file.equals(regularFile)) {
UtilSystem.recoverFileIfRequired(regularFile);
}
Log.debug("Loading: " + file.getName());
if (!file.exists()) {
throw new JajukException(5, file.toString());
}
lTime = System.currentTimeMillis();
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setValidating(false);
spf.setNamespaceAware(false);
// See http://xerces.apache.org/xerces-j/features.html for details
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://xml.org/sax/features/string-interning", true);
SAXParser saxParser = spf.newSAXParser();
saxParser.parse(file.toURI().toURL().toString(), getInstance());
}
/**
* Perform a collection clean up for logical items ( delete orphan data ) Note
* that we don't cleanup genres up because we want to keep genres even without
* associated tracks for ambiences for instance.
*/
public static synchronized void cleanupLogical() {
// Tracks cleanup
TrackManager.getInstance().cleanup();
// Artists cleanup
ArtistManager.getInstance().cleanup();
// Album-artist cleanup
AlbumArtistManager.getInstance().cleanup();
// albums cleanup
AlbumManager.getInstance().cleanup();
// years cleanup
YearManager.getInstance().cleanup();
}
/**
* Clear the full collection Note that we don't clear TypeManager as it is not
* read from a file but filled programmatically.
*/
public static synchronized void clearCollection() {
TrackManager.getInstance().clear();
GenreManager.getInstance().clear();
ArtistManager.getInstance().clear();
AlbumArtistManager.getInstance().clear();
AlbumManager.getInstance().clear();
YearManager.getInstance().clear();
FileManager.getInstance().clear();
DirectoryManager.getInstance().clear();
PlaylistManager.getInstance().clear();
DeviceManager.getInstance().clear();
}
/**
* parsing warning.
*
* @param spe
* @throws SAXException the SAX exception
*/
@Override
@SuppressWarnings("ucd")
//NOSONAR
public void warning(SAXParseException spe) throws SAXException {
throw new SAXException(Messages.getErrorMessage(5) + " / " + spe.getSystemId() + "/"
+ spe.getLineNumber() + "/" + spe.getColumnNumber() + " : " + spe.getMessage());
}
/**
* parsing error.
*
* @param spe
* @throws SAXException the SAX exception
*/
@Override
@SuppressWarnings("ucd")
public void error(SAXParseException spe) throws SAXException {
throw new SAXException(Messages.getErrorMessage(5) + " / " + spe.getSystemId() + "/"
+ spe.getLineNumber() + "/" + spe.getColumnNumber() + " : " + spe.getMessage());
}
/**
* parsing fatal error.
*
* @param spe
* @throws SAXException the SAX exception
*/
@Override
@SuppressWarnings("ucd")
public void fatalError(SAXParseException spe) throws SAXException {
throw new SAXException(Messages.getErrorMessage(5) + " / " + spe.getSystemId() + "/"
+ spe.getLineNumber() + "/" + spe.getColumnNumber() + " : " + spe.getMessage());
}
/**
* Called at parsing start.
*/
@Override
@SuppressWarnings("ucd")
public void startDocument() {
Log.debug("Starting collection file parsing...");
}
/**
* Called at parsing end.
*/
@Override
@SuppressWarnings("ucd")
public void endDocument() {
long l = (System.currentTimeMillis() - lTime);
Log.debug("Collection file parsing done : " + l + " ms");
}
/**
* Called when we start an element intern() method use policy : we use this
* method when adding a new string into JVM that will probably be referenced
* by several objects like the Genre ID that is referenced by many tracks. In
* this case, all the String objects share the same char[]. On another hand,
* it musn't be used for strings that have low probability to be used several
* times (like raw names) as it uses a lot of CPU (equals() is called) and we
* want startup to be as fast as possible. Note that the use of intern() save
* around 1/4 of overall heap memory
*
* We use sax-interning for the main items sections (<styles> for ie). For all
* raw items, we don't perform equals on item name but we compare the string
* hashcode
*
* @param sUri
* @param s
* @param sQName
* @param attributes
*
* @throws SAXException the SAX exception
*/
@Override
public void startElement(String sUri, String s, String sQName, Attributes attributes)
throws SAXException {
try {
int idIndex = attributes.getIndex(Const.XML_ID);
// [PERF] Manage top tags to set current stage. Manages 'properties'
// tags as well
if (idIndex == -1) {
// Note that we compare string with '==' for performance reasons and it is safe here.
if (Const.XML_DEVICES == sQName) { //NOSONAR
manager = DeviceManager.getInstance();
stage = Stage.STAGE_DEVICES;
needCheckID = true;
} else if (Const.XML_ALBUMS == sQName) {//NOSONAR
manager = AlbumManager.getInstance();
stage = Stage.STAGE_ALBUMS;
needCheckID = true;
} else if (Const.XML_ARTISTS == sQName) {//NOSONAR
manager = ArtistManager.getInstance();
stage = Stage.STAGE_ARTISTS;
needCheckID = true;
} else if (Const.XML_ALBUM_ARTISTS == sQName) {//NOSONAR
manager = AlbumArtistManager.getInstance();
stage = Stage.STAGE_ALBUM_ARTIST;
needCheckID = true;
} else if (Const.XML_DIRECTORIES == sQName) {//NOSONAR
manager = DirectoryManager.getInstance();
stage = Stage.STAGE_DIRECTORIES;
needCheckID = true;
} else if (Const.XML_FILES == sQName) {//NOSONAR
manager = FileManager.getInstance();
stage = Stage.STAGE_FILES;
needCheckID = true;
} else if (Const.XML_PLAYLISTS == sQName) {//NOSONAR
// This code is here for Jajuk < 1.6 compatibility
manager = PlaylistManager.getInstance();
stage = Stage.STAGE_PLAYLISTS;
needCheckID = true;
} else if (Const.XML_PLAYLIST_FILES == sQName) {//NOSONAR
manager = PlaylistManager.getInstance();
stage = Stage.STAGE_PLAYLIST_FILES;
needCheckID = true;
} else if (Const.XML_GENRES == sQName) {//NOSONAR
manager = GenreManager.getInstance();
stage = Stage.STAGE_GENRES;
needCheckID = true;
} else if (Const.XML_TRACKS == sQName) {//NOSONAR
manager = TrackManager.getInstance();
stage = Stage.STAGE_TRACKS;
needCheckID = true;
} else if (Const.XML_YEARS == sQName) {//NOSONAR
manager = YearManager.getInstance();
stage = Stage.STAGE_YEARS;
needCheckID = true;
} else if (Const.XML_TYPES == sQName) {//NOSONAR
// This is here for pre-1.7 collections, after we don't commit types
// anymore (they are set programmatically)
manager = TypeManager.getInstance();
stage = Stage.STAGE_TYPES;
needCheckID = false;
} else if (Const.XML_PROPERTY == sQName) {//NOSONAR
// A property description
boolean bCustom = Boolean.parseBoolean(attributes.getValue(attributes
.getIndex(Const.XML_CUSTOM)));
boolean bConstructor = Boolean.parseBoolean(attributes.getValue(attributes
.getIndex(Const.XML_CONSTRUCTOR)));
boolean bShouldBeDisplayed = Boolean.parseBoolean(attributes.getValue(attributes
.getIndex(Const.XML_VISIBLE)));
boolean bEditable = Boolean.parseBoolean(attributes.getValue(attributes
.getIndex(Const.XML_EDITABLE)));
boolean bUnique = Boolean.parseBoolean(attributes.getValue(attributes
.getIndex(Const.XML_UNIQUE)));
Class<?> cType = Class.forName(attributes.getValue(Const.XML_TYPE));
String sDefaultValue = attributes.getValue(Const.XML_DEFAULT_VALUE).intern();
Object oDefaultValue = null;
if (sDefaultValue != null && sDefaultValue.length() > 0) {
try {
// Date format has changed from 1.3 (only yyyyMMdd
// addition format is used)
// so an exception will be thrown when upgrading
// from 1.2
// we reset default value to "today"
oDefaultValue = UtilString.parse(sDefaultValue, cType);
} catch (ParseException e) {
oDefaultValue = new Date();
}
}
String sPropertyName = attributes.getValue(Const.XML_NAME).intern();
if (manager.getMetaInformation(sPropertyName) == null) {
PropertyMetaInformation meta = new PropertyMetaInformation(sPropertyName, bCustom,
bConstructor, bShouldBeDisplayed, bEditable, bUnique, cType, oDefaultValue);
// standard properties are already loaded
manager.registerProperty(meta);
}
}
if (Const.XML_PROPERTY == sQName) {//NOSONAR
Log.debug("Found property: " + attributes.getValue(Const.XML_NAME));
} else {
Log.debug("Starting stage: '" + stage + "' with property: '" + sQName + "' manager: "
+ (manager != null ? manager.getXMLTag() : "<null>"));
}
} else {
// Manage elements themselves using a switch for performances
switch (stage) {
case STAGE_FILES:
handleFiles(attributes, idIndex);
break;
case STAGE_DIRECTORIES:
handleDirectories(attributes, idIndex);
break;
case STAGE_TRACKS:
handleTracks(attributes, idIndex);
break;
case STAGE_ALBUMS:
handleAlbums(attributes, idIndex);
break;
case STAGE_ARTISTS:
handleArtists(attributes, idIndex);
break;
case STAGE_ALBUM_ARTIST:
handleAlbumArtists(attributes, idIndex);
break;
case STAGE_GENRES:
handleGenres(attributes, idIndex);
break;
case STAGE_PLAYLIST_FILES:
handlePlaylistFiles(attributes, idIndex);
break;
case STAGE_DEVICES:
handleDevices(attributes, idIndex);
break;
case STAGE_YEARS:
handleYears(attributes, idIndex);
break;
case STAGE_TYPES:
Log.warn("Unexpected Stage: STAGE_TYPES");
break;
default:
Log.warn("Unexpected Stage: " + stage);
}
}
} catch (Throwable e) {//NOSONAR
// Make sure to catch every issue here (including runtime exceptions) so we make sure to start
// jajuk
StringBuilder sAttributes = new StringBuilder();
for (int i = 0; i < attributes.getLength(); i++) {
sAttributes.append('\n').append(attributes.getQName(i)).append('=')
.append(attributes.getValue(i));
}
Log.error(5, sAttributes.toString(), e);
}
}
/**
* Handle files.
*
* @param attributes
* @param idIndex
*/
private void handleFiles(Attributes attributes, int idIndex) {
String sItemName = attributes.getValue(Const.XML_NAME);
// Check file type is still registered, it can be
// useful for ie if mplayer is no more available
String ext = UtilSystem.getExtension(sItemName);
Type type = TypeManager.getInstance().getTypeByExtension(ext);
if (type == null) {
return;
}
String sTrackId = attributes.getValue(Const.XML_TRACK).intern();
// UPGRADE check if track Id is right
if ((hmWrongRightTrackID.size() > 0) &&
// replace wrong by right ID
(hmWrongRightTrackID.containsKey(sTrackId))) {
sTrackId = hmWrongRightTrackID.get(sTrackId);
}
Track track = TrackManager.getInstance().getTrackByID(sTrackId);
String sParentID = attributes.getValue(Const.XML_DIRECTORY).intern();
// UPGRADE check parent ID is right
if ((hmWrongRightDirectoryID.size() > 0) &&
// replace wrong by right ID
(hmWrongRightDirectoryID.containsKey(sParentID))) {
sParentID = hmWrongRightDirectoryID.get(sParentID);
}
Directory dParent = DirectoryManager.getInstance().getDirectoryByID(sParentID);
if (dParent == null || track == null) { // more checkups
return;
}
String size = attributes.getValue(Const.XML_SIZE);
long lSize = 0;
if (size != null) {
lSize = Long.parseLong(size);
}
// Quality analyze, handle format problems (mainly for
// upgrades)
long lQuality = 0;
try {
String sQuality = attributes.getValue(Const.XML_QUALITY);
if (sQuality != null) {
lQuality = UtilString.fastLongParser(sQuality);
}
} catch (Exception e) {
if (Log.isDebugEnabled()) {
// wrong format
Log.debug(Messages.getString("Error.137") + ":" + sItemName + " Value: "
+ attributes.getValue(Const.XML_QUALITY) + " Error:" + e.getMessage());
}
}
String sID = attributes.getValue(idIndex).intern();
/*
* UPGRADE test : if first element we check has the right ID, we avoid wasting time checking
* others item one. If is is an upgrade, we force the check.We always check id in debug mode.
*/
String sRightID = sID;
if (needCheckID) {
sRightID = FileManager.createID(sItemName, dParent).intern();
if (sRightID == sID) { //NOSONAR
needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
} else {
Log.debug("** Wrong file Id, upgraded: " + sItemName);
hmWrongRightFileID.put(sID, sRightID);
}
}
org.jajuk.base.File file = FileManager.getInstance().registerFile(sRightID, sItemName, dParent,
track, lSize, lQuality);
file.populateProperties(attributes);
}
/**
* Handle directories.
*
* @param attributes
* @param idIndex
*/
private void handleDirectories(Attributes attributes, int idIndex) {
Directory dParent = null;
// dParent = null;
String sParentID = attributes.getValue(Const.XML_DIRECTORY_PARENT).intern();
// UPGRADE
if ((hmWrongRightDirectoryID.size() > 0) && (hmWrongRightDirectoryID.containsKey(sParentID))) {
sParentID = hmWrongRightDirectoryID.get(sParentID);
}
// We use intern() here for performances
if (sParentID != "-1") { //NOSONAR
// Parent directory should be already referenced
// because of order conservation
dParent = DirectoryManager.getInstance().getDirectoryByID(sParentID);
// check parent directory exists
if (dParent == null) {
return;
}
}
String sDeviceID = attributes.getValue(Const.XML_DEVICE).intern();
// take upgraded device ID if needed
if ((hmWrongRightDeviceID.size() > 0) && (hmWrongRightDeviceID.containsKey(sDeviceID))) {
sDeviceID = hmWrongRightDeviceID.get(sDeviceID);
}
Device device = DeviceManager.getInstance().getDeviceByID(sDeviceID);
if (device == null) { // check device exists
return;
}
String sItemName = attributes.getValue(Const.XML_NAME);
String sID = attributes.getValue(idIndex).intern();
// UPGRADE test
String sRightID = sID;
if (needCheckID) {
sRightID = DirectoryManager.createID(sItemName, device, dParent).intern();
if (sRightID == sID) {//NOSONAR
needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
} else {
Log.debug("** Wrong directory Id, upgraded: " + sItemName);
hmWrongRightDirectoryID.put(sID, sRightID);
}
}
Directory directory = DirectoryManager.getInstance().registerDirectory(sRightID, sItemName,
dParent, device);
directory.populateProperties(attributes);
// also remember top-level directories at the device
if (dParent == null) {
device.addDirectory(directory);
}
}
/**
* Handle tracks.
*
* @param attributes
* @param idIndex
*
* @throws ParseException the parse exception
*/
private void handleTracks(Attributes attributes, int idIndex) throws ParseException {
String sID = attributes.getValue(idIndex).intern();
String sTrackName = attributes.getValue(Const.XML_TRACK_NAME);
// album
String sAlbumID = attributes.getValue(Const.XML_TRACK_ALBUM).intern();
if ((hmWrongRightAlbumID.size() > 0) && (hmWrongRightAlbumID.containsKey(sAlbumID))) {
sAlbumID = hmWrongRightAlbumID.get(sAlbumID);
}
Album album = AlbumManager.getInstance().getAlbumByID(sAlbumID);
// Genre
String sGenreID = attributes.getValue(Const.XML_TRACK_GENRE).intern();
if ((hmWrongRightGenreID.size() > 0) && (hmWrongRightGenreID.containsKey(sGenreID))) {
sGenreID = hmWrongRightGenreID.get(sGenreID);
}
Genre genre = GenreManager.getInstance().getGenreByID(sGenreID);
// Year
String sYearID = attributes.getValue(Const.XML_TRACK_YEAR).intern();
Year year = YearManager.getInstance().getYearByID(sYearID);
// For jajuk < 1.4
if (year == null) {
year = YearManager.getInstance().registerYear(sYearID, sYearID);
}
// Artist
String sArtistID = attributes.getValue(Const.XML_TRACK_ARTIST).intern();
if ((hmWrongRightArtistID.size() > 0) && (hmWrongRightArtistID.containsKey(sArtistID))) {
sArtistID = hmWrongRightArtistID.get(sArtistID);
}
Artist artist = ArtistManager.getInstance().getArtistByID(sArtistID);
// Album-artist (not a constructor level property)
String sAlbumArtist = attributes.getValue(Const.XML_ALBUM_ARTIST);
if (StringUtils.isNotBlank(sAlbumArtist)) {
sAlbumArtist = sAlbumArtist.intern();
}
if ((hmWrongRightAlbumArtistID.size() > 0)
&& (hmWrongRightAlbumArtistID.containsKey(sAlbumArtist))) {
sAlbumArtist = hmWrongRightAlbumArtistID.get(sAlbumArtist);
}
// Note that when upgrading from jajuk < 1.9, album artists field is alway null, call on the
// next line always return null
AlbumArtist albumArtist = AlbumArtistManager.getInstance().getAlbumArtistByID(sAlbumArtist);
if (albumArtist == null) {
// we force album artist to this default, a deep scan will be required to get actual values
albumArtist = AlbumArtistManager.getInstance().registerAlbumArtist(Const.UNKNOWN_ARTIST);
}
// Length
long length = UtilString.fastLongParser(attributes.getValue(Const.XML_TRACK_LENGTH));
// Type
String typeID = attributes.getValue(Const.XML_TYPE).intern();
if (needCheckConversions) {
if (CONVERSION.containsKey(typeID)) {
typeID = CONVERSION.get(typeID);
} else {
needCheckConversions = false;
}
}
Type type = TypeManager.getInstance().getTypeByID(typeID);
// more checkups
if (album == null || artist == null) {
return;
}
if (genre == null || type == null) {
return;
}
// Idem for order
long lOrder = 0l;
try {
lOrder = UtilString.fastLongParser(attributes.getValue(Const.XML_TRACK_ORDER));
} catch (Exception e) {
if (Log.isDebugEnabled()) {
// wrong format
Log.debug(Messages.getString("Error.137") + ":" + sTrackName); // wrong
}
}
// Idem for disc number
long lDiscNumber = 0l;
if (attributes.getValue(Const.XML_TRACK_DISC_NUMBER) != null) {
try {
lDiscNumber = UtilString.fastLongParser(attributes.getValue(Const.XML_TRACK_DISC_NUMBER));
} catch (Exception e) {
if (Log.isDebugEnabled()) {
// wrong format
Log.debug(Messages.getString("Error.137") + ":" + sTrackName);
}
}
}
// UPGRADE test
String sRightID = sID;
if (needCheckID) {
sRightID = TrackManager.createID(sTrackName, album, genre, artist, length, year, lOrder,
type, lDiscNumber).intern();
if (sRightID == sID) {//NOSONAR
needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
} else {
Log.debug("** Wrong Track Id, upgraded: " + sTrackName);
hmWrongRightTrackID.put(sID, sRightID);
}
}
Track track = TrackManager.getInstance().registerTrack(sRightID, sTrackName, album, genre,
artist, length, year, lOrder, type, lDiscNumber);
TrackManager.getInstance().changeTrackRate(track,
UtilString.fastLongParser(attributes.getValue(Const.XML_TRACK_RATE)));
track.setHits(UtilString.fastLongParser(attributes.getValue(Const.XML_TRACK_HITS)));
// only set discovery date if it is available in the file
if (attributes.getValue(Const.XML_TRACK_DISCOVERY_DATE) != null) {
// Date format should be OK
Date dAdditionDate = additionFormatter.parse(attributes
.getValue(Const.XML_TRACK_DISCOVERY_DATE));
track.setDiscoveryDate(dAdditionDate);
}
String sComment = attributes.getValue(Const.XML_TRACK_COMMENT);
if (sComment == null) {
sComment = "";
}
track.setComment(sComment.intern());
track.setAlbumArtist(albumArtist);
track.populateProperties(attributes);
}
/**
* Handle albums.
*
* @param attributes
* @param idIndex
*/
private void handleAlbums(Attributes attributes, int idIndex) {
String sID = attributes.getValue(idIndex).intern();
String sItemName = attributes.getValue(Const.XML_NAME).intern();
String sAttributeAlbumArtist = attributes.getValue(Const.XML_ALBUM_ARTIST);
if (sAttributeAlbumArtist != null) {
// Make sure to store the string into the String pool to save memory
sAttributeAlbumArtist.intern();//NOSONAR
}
long lItemDiscID = 0;
String sAttributeDiskId = attributes.getValue(Const.XML_ALBUM_DISC_ID);
if (sAttributeDiskId != null) {
lItemDiscID = Long.parseLong(sAttributeDiskId);
}
// UPGRADE test
String sRightID = sID;
if (needCheckID) {
sRightID = AlbumManager.createID(sItemName, lItemDiscID).intern();
if (sRightID == sID) {//NOSONAR
needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
} else {
Log.debug("** Wrong album Id, upgraded: " + sItemName);
hmWrongRightAlbumID.put(sID, sRightID);
}
}
Album album = AlbumManager.getInstance().registerAlbum(sRightID, sItemName, lItemDiscID);
if (album != null) {
album.populateProperties(attributes);
}
}
/**
* Handle artists.
*
* @param attributes
* @param idIndex
*/
private void handleArtists(Attributes attributes, int idIndex) {
String sID = attributes.getValue(idIndex).intern();
String sItemName = attributes.getValue(Const.XML_NAME).intern();
// UPGRADE test
String sRightID = sID;
if (needCheckID) {
sRightID = ItemManager.createID(sItemName).intern();
if (sRightID == sID) {//NOSONAR
needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
} else {
Log.debug("** Wrong artist Id, upgraded: " + sItemName);
hmWrongRightArtistID.put(sID, sRightID);
}
}
Artist artist = ArtistManager.getInstance().registerArtist(sRightID, sItemName);
if (artist != null) {
artist.populateProperties(attributes);
}
}
/**
* Handle genres.
*
* @param attributes
* @param idIndex
*/
private void handleGenres(Attributes attributes, int idIndex) {
String sID = attributes.getValue(idIndex).intern();
String sItemName = attributes.getValue(Const.XML_NAME).intern();
// UPGRADE test
String sRightID = sID;
if (needCheckID) {
sRightID = ItemManager.createID(sItemName).intern();
if (sRightID == sID) {//NOSONAR
needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
} else {
Log.debug("** Wrong genre Id, upgraded: " + sItemName);
hmWrongRightGenreID.put(sID, sRightID);
}
}
Genre genre = GenreManager.getInstance().registerGenre(sRightID, sItemName);
if (genre != null) {
genre.populateProperties(attributes);
}
}
/**
* Handle playlist files.
*
* @param attributes
* @param idIndex
*/
private void handlePlaylistFiles(Attributes attributes, int idIndex) {
String sParentID = attributes.getValue(Const.XML_DIRECTORY).intern();
// UPGRADE check parent ID is right
if ((hmWrongRightDirectoryID.size() > 0) &&
// replace wrong by right ID
(hmWrongRightDirectoryID.containsKey(sParentID))) {
sParentID = hmWrongRightDirectoryID.get(sParentID);
}
Directory dParent = DirectoryManager.getInstance().getDirectoryByID(sParentID);
if (dParent == null) { // check directory is exists
return;
}
String sID = attributes.getValue(idIndex).intern();
String sItemName = attributes.getValue(Const.XML_NAME);
// UPGRADE test
String sRightID = sID;
if (needCheckID) {
sRightID = PlaylistManager.createID(sItemName, dParent).intern();
if (sRightID == sID) {//NOSONAR
needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
} else {
Log.debug("** Wrong playlist Id, upgraded: " + sItemName);
hmWrongRightPlaylistFileID.put(sID, sRightID);
}
}
Playlist plf = PlaylistManager.getInstance().registerPlaylistFile(sRightID, sItemName, dParent);
if (plf != null) {
plf.populateProperties(attributes);
}
}
/**
* Handle devices.
*
* @param attributes
* @param idIndex
*/
private void handleDevices(Attributes attributes, int idIndex) {
String sID = attributes.getValue(idIndex).intern();
String sItemName = attributes.getValue(Const.XML_NAME);
long lType = UtilString.fastLongParser(attributes.getValue(Const.XML_TYPE));
Device.Type type = Device.Type.values()[(int) lType];
// UPGRADE test
String sRightID = sID;
if (needCheckID) {
sRightID = ItemManager.createID(sItemName).intern();
if (sRightID == sID) {//NOSONAR
needCheckID = UpgradeManager.isUpgradeDetected() || SessionService.isTestMode();
} else {
Log.debug("** Wrong device Id, upgraded: " + sItemName);
hmWrongRightDeviceID.put(sID, sRightID);
}
}
String sURL = attributes.getValue(Const.XML_URL);
Device device = DeviceManager.getInstance().registerDevice(sRightID, sItemName, type, sURL);
if (device != null) {
device.populateProperties(attributes);
}
}
/**
* Handle years.
*
* @param attributes
* @param idIndex
*/
private void handleYears(Attributes attributes, int idIndex) {
String sID = attributes.getValue(idIndex).intern();
String sItemName = attributes.getValue(Const.XML_NAME).intern();
Year year = YearManager.getInstance().registerYear(sID, sItemName);
if (year != null) {
year.populateProperties(attributes);
}
}
/**
* Handle album artists.
*
* @param attributes
* @param idIndex
*/
private void handleAlbumArtists(Attributes attributes, int idIndex) {
String sID = attributes.getValue(idIndex).intern();
String sItemName = attributes.getValue(Const.XML_NAME).intern();
AlbumArtist albumArtist = AlbumArtistManager.getInstance().registerAlbumArtist(sID, sItemName);
if (albumArtist != null) {
albumArtist.populateProperties(attributes);
}
}
/**
* Gets the hm wrong right file id.
*
* @return list of wrong file id (used by history)
*/
public Map<String, String> getHmWrongRightFileID() {
return hmWrongRightFileID;
}
/**
* Gets the wrong right album i ds.
*
* @return the wrong right album i ds
*/
public Map<String, String> getWrongRightAlbumIDs() {
return this.hmWrongRightAlbumID;
}
}