org.lnicholls.galleon.apps.iTunes.PlaylistParser.java Source code

Java tutorial

Introduction

Here is the source code for org.lnicholls.galleon.apps.iTunes.PlaylistParser.java

Source

package org.lnicholls.galleon.apps.iTunes;

/*
    
 * Copyright (C) 2005 Leon Nicholls
    
 * 
    
 * 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 (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, write to the Free
    
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
    
 * 
    
 * See the file "COPYING" for more details.
    
 */

import java.io.*;

import java.io.FileInputStream;

import java.io.IOException;

import java.net.URLDecoder;

import java.text.SimpleDateFormat;

import java.util.ArrayList;

import java.util.Date;

import java.util.HashMap;

import java.util.List;

import java.util.Iterator;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Session;

import net.sf.hibernate.Transaction;

import org.apache.commons.lang.SystemUtils;

import org.apache.log4j.Logger;

import org.lnicholls.galleon.database.Audio;

import org.lnicholls.galleon.database.AudioManager;

import org.lnicholls.galleon.database.HibernateUtil;

import org.lnicholls.galleon.database.Playlists;

import org.lnicholls.galleon.database.PlaylistsManager;

import org.lnicholls.galleon.database.PlaylistsTracks;

import org.lnicholls.galleon.database.PlaylistsTracksManager;

import org.lnicholls.galleon.media.Mp3File;

import org.lnicholls.galleon.util.Tools;

import org.xml.sax.Attributes;

import org.xml.sax.InputSource;

import org.xml.sax.SAXException;

import org.xml.sax.SAXParseException;

import org.xml.sax.XMLReader;

import org.xml.sax.helpers.DefaultHandler;

import org.xml.sax.helpers.XMLReaderFactory;

public class PlaylistParser {

    private static Logger log = Logger.getLogger(PlaylistParser.class.getName());

    private static String DICT = "dict";

    private static String KEY = "key";

    public PlaylistParser(String path) {

        try {

            //path = "D:/galleon/iTunes Music Library.xml";

            ArrayList currentPlaylists = new ArrayList();

            // Read all tracks

            XMLReader trackReader = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");

            TrackParser trackParser = new TrackParser();

            trackReader.setContentHandler(trackParser);

            trackReader.setErrorHandler(trackParser);

            trackReader.setFeature("http://xml.org/sax/features/validation", false);

            File file = new File(path);

            if (file.exists()) {

                InputStream inputStream = Tools.getInputStream(file);

                trackReader.parse(new InputSource(inputStream));

                inputStream.close();

            }

            // Read all playlists

            XMLReader listReader = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");

            ListParser listParser = new ListParser(currentPlaylists);

            listReader.setContentHandler(listParser);

            listReader.setErrorHandler(listParser);

            listReader.setFeature("http://xml.org/sax/features/validation", false);

            file = new File(path);

            if (file.exists()) {

                InputStream inputStream = Tools.getInputStream(file);

                listReader.parse(new InputSource(inputStream));

                inputStream.close();

            }

            // Remove old playlists

            List list = PlaylistsManager.listAll();

            if (list != null && list.size() > 0)

            {

                Iterator playlistIterator = list.iterator();

                while (playlistIterator.hasNext())

                {

                    Playlists playlist = (Playlists) playlistIterator.next();

                    boolean found = false;

                    Iterator iterator = currentPlaylists.iterator();

                    while (iterator.hasNext())

                    {

                        String externalId = (String) iterator.next();

                        if (externalId.equals(playlist.getExternalId()))

                        {

                            found = true;

                            break;

                        }

                    }

                    if (!found)

                    {

                        PlaylistsManager.deletePlaylistsTracks(playlist);

                        PlaylistsManager.deletePlaylists(playlist);

                        log.debug("Removed playlist: " + playlist.getTitle());

                    }

                }

                list.clear();

            }

            currentPlaylists.clear();

        } catch (IOException ex) {

            Tools.logException(PlaylistParser.class, ex);

        } catch (SAXException ex) {

            Tools.logException(PlaylistParser.class, ex);

        } catch (Exception ex) {

            Tools.logException(PlaylistParser.class, ex);

        }

    }

    class TrackParser extends DefaultHandler

    {

        public TrackParser()

        {

            super();

            mTracks = new HashMap();

            mKey = new StringBuffer(100);

            mValue = new StringBuffer(100);

        }

        /**
            
         * Method used by SAX at the start of the XML document parsing.
            
         */

        public void startDocument() {

        }

        /**
            
         * SAX XML parser method. Start of an element.
            
         * 
            
         * @param namespaceURI
            
         *            namespace URI this element is associated with, or an empty String
            
         * @param localName
            
         *            name of element (with no namespace prefix, if one is present)
            
         * @param qName
            
         *            XML 1.0 version of element name: [namespace prefix]:[localName]
            
         * @param attributes
            
         *            Attributes for this element
            
         */

        public void startElement(String namespaceURI, String localName, String qName, Attributes attributes)

                throws SAXException {

            if (localName.equals(DICT)) {

                mDictLevel = mDictLevel + 1;

            } else if (localName.equals(KEY)) {

                mInKey = true;

                mKey.setLength(0);

            }

            if (mLastTag != null && mLastTag.equals(KEY) && !localName.equals(KEY) && !localName.equals(DICT)) {

                mInValue = true;

                mValue.setLength(0);

                mType = localName;

            } else if (mFoundTracks && mDictLevel == 3 && localName.equals(DICT)) {

                mTracks.clear();

            }

        }

        public void characters(char[] ch, int start, int length) throws SAXException {

            if (mInKey) {

                mKey.append(new String(ch, start, length).trim());

            } else if (mInValue) {

                mValue.append(new String(ch, start, length).trim());

            }

        }

        /**
            
         * SAX XML parser method. End of an element.
            
         * 
            
         * @param namespaceURI
            
         *            namespace URI this element is associated with, or an empty String
            
         * @param localName
            
         *            name of element (with no namespace prefix, if one is present)
            
         * @param qName
            
         *            XML 1.0 version of element name: [namespace prefix]:[localName]
            
         * @param attributes
            
         *            Attributes for this element
            
         */

        public void endElement(String namespaceURI, String localName, String qName) throws SAXException {

            mLastTag = localName;

            if (mDictLevel == 1 && localName.equals(KEY) && same(mKey, "Tracks")) {

                mFoundTracks = true;

                mFoundPlaylists = false;

            } else if (mDictLevel == 1 && localName.equals(KEY) && same(mKey, "Playlists")) {

                mFoundTracks = false;

                mFoundPlaylists = true;

            } else if (mFoundTracks && mDictLevel == 3 && localName.equals(DICT)) {

                processTrack(mTracks);

                mTracks.clear();

                if (++mCounter % 100 == 0)

                    System.gc();

                try {

                    Thread.sleep(50); // give the CPU some breathing time

                } catch (Exception ex) {

                }

            }

            if (localName.equals(DICT)) {

                mDictLevel = mDictLevel - 1;

            } else if (localName.equals(KEY)) {

                mInKey = false;

            } else if (mType != null && localName.equals(mType)) {

                if (mFoundTracks) {

                    mTracks.put(mKey.toString(), mValue.toString());

                }

                mInValue = false;

            }

        }

        /**
            
         * Method used by SAX at the end of the XML document parsing.
            
         */

        public void endDocument() {

        }

        /**
            
         * Receive notification of a parser warning.
            
         * 
            
         * <p>
            
         * The default implementation does nothing. Application writers may override this method in a subclass to take
            
         * specific actions for each warning, such as inserting the message in a log file or printing it to the console.
            
         * </p>
            
         * 
            
         * @param e
            
         *            The warning information encoded as an exception.
            
         * @exception org.xml.sax.SAXException
            
         *                Any SAX exception, possibly wrapping another exception.
            
         * @see org.xml.sax.ErrorHandler#warning
            
         * @see org.xml.sax.SAXParseException
            
         */

        public void warning(SAXParseException e) throws SAXException {

            throw e;

        }

        /**
            
         * Receive notification of a recoverable parser error.
            
         * 
            
         * <p>
            
         * The default implementation does nothing. Application writers may override this method in a subclass to take
            
         * specific actions for each error, such as inserting the message in a log file or printing it to the console.
            
         * </p>
            
         * 
            
         * @param e
            
         *            The warning information encoded as an exception.
            
         * @exception org.xml.sax.SAXException
            
         *                Any SAX exception, possibly wrapping another exception.
            
         * @see org.xml.sax.ErrorHandler#warning
            
         * @see org.xml.sax.SAXParseException
            
         */

        public void error(SAXParseException e) throws SAXException {

            throw e;

        }

        /**
            
         * Report a fatal XML parsing error.
            
         * 
            
         * <p>
            
         * The default implementation throws a SAXParseException. Application writers may override this method in a subclass
            
         * if they need to take specific actions for each fatal error (such as collecting all of the errors into a single
            
         * report): in any case, the application must stop all regular processing when this method is invoked, since the
            
         * document is no longer reliable, and the parser may no longer report parsing events.
            
         * </p>
            
         * 
            
         * @param e
            
         *            The error information encoded as an exception.
            
         * @exception org.xml.sax.SAXException
            
         *                Any SAX exception, possibly wrapping another exception.
            
         * @see org.xml.sax.ErrorHandler#fatalError
            
         * @see org.xml.sax.SAXParseException
            
         */

        public void fatalError(SAXParseException e) throws SAXException {

            throw e;

        }

        private void processTrack(HashMap track) {

            Audio audio = new Audio();

            Mp3File.defaultProperties(audio); // no size??

            String location = null;

            if (track.containsKey("Location")) {

                location = decode((String) track.get("Location"));

                if (location.endsWith("/"))

                    location = location.substring(0, location.length() - 1);

                // TODO ignore others?

                if (location.startsWith("file://localhost/")) {

                    if (SystemUtils.IS_OS_MAC_OSX)

                        location = location.substring("file://localhost".length(), location.length());

                    else

                        location = location.substring("file://localhost/".length(), location.length());

                    try {

                        File file = new File(location);

                        if (file.exists() && file.getName().toLowerCase().endsWith(".mp3")) {

                            location = file.getCanonicalPath();

                            audio.setPath(location);

                        } else

                            return;

                    } catch (Exception ex) {

                        Tools.logException(PlaylistParser.class, ex);

                    }

                } else

                    audio.setPath(location); // http

            }

            if (location != null) {

                String externalId = null;

                boolean found = false;

                if (track.containsKey("Track ID")) {

                    externalId = decode((String) track.get("Track ID"));

                    /*
                        
                    try {
                        
                         List list = AudioManager.findByExternalId(externalId);
                        
                         if (list != null && list.size() > 0) {
                        
                     audio = (Audio) list.get(0);
                        
                      list.clear();
                        
                      found = true;
                        
                         }
                        
                     } catch (Exception ex) {
                        
                         Tools.logException(PlaylistParser.class, ex);
                        
                     }
                        
                     */

                }

                if (!found)

                {

                    try {

                        List list = AudioManager.findByPath(location);

                        if (list != null && list.size() > 0) {

                            audio = (Audio) list.get(0);

                            list.clear();

                        }

                    } catch (Exception ex) {

                        Tools.logException(PlaylistParser.class, ex);

                    }

                }

                try {

                    if (track.containsKey("Date Modified")) {

                        Date modified = mDateFormat.parse((String) track.get("Date Modified"));

                        if (!audio.getDateModified().equals(modified))

                            audio.setDateModified(modified);

                        else {

                            return;

                        }

                    }

                } catch (Exception ex) {

                    Tools.logException(PlaylistParser.class, ex);

                }

                if (track.containsKey("Track ID")) {

                    audio.setExternalId(externalId);

                }

                if (track.containsKey("Name")) {

                    audio.setTitle(decode((String) track.get("Name")));

                }

                if (track.containsKey("Artist")) {

                    audio.setArtist(decode((String) track.get("Artist")));

                }

                if (track.containsKey("Album")) {

                    audio.setAlbum(decode((String) track.get("Album")));

                }

                if (track.containsKey("Genre")) {

                    audio.setGenre(decode((String) track.get("Genre")));

                }

                try {

                    if (track.containsKey("Size")) {

                        audio.setSize(new Integer((String) track.get("Size")).intValue());

                    }

                } catch (Exception ex) {

                    Tools.logException(PlaylistParser.class, ex);

                }

                try {

                    if (track.containsKey("Total Time")) {

                        audio.setDuration((new Integer((String) track.get("Total Time")).intValue()));

                    }

                } catch (Exception ex) {

                    Tools.logException(PlaylistParser.class, ex);

                }

                try {

                    if (track.containsKey("Rating")) {

                        audio.setRating(new Integer((String) track.get("Rating")).intValue() / 20);

                    }

                } catch (Exception ex) {

                    Tools.logException(PlaylistParser.class, ex);

                }

                try {

                    if (track.containsKey("Track Number")) {

                        audio.setTrack(new Integer((String) track.get("Track Number")).intValue());

                    }

                } catch (Exception ex) {

                    Tools.logException(PlaylistParser.class, ex);

                }

                try {

                    if (track.containsKey("Year")) {

                        audio.setDate(new Integer((String) track.get("Year")).intValue());

                    }

                } catch (Exception ex) {

                    Tools.logException(PlaylistParser.class, ex);

                }

                try {

                    if (track.containsKey("Date Added")) {

                        audio.setDateAdded(mDateFormat.parse((String) track.get("Date Added")));

                    }

                } catch (Exception ex) {

                    Tools.logException(PlaylistParser.class, ex);

                }

                try {

                    if (track.containsKey("Bit Rate")) {

                        audio.setBitRate(new Integer((String) track.get("Bit Rate")).intValue());

                    }

                } catch (Exception ex) {

                    Tools.logException(PlaylistParser.class, ex);

                }

                try {

                    if (track.containsKey("Sample Rate")) {

                        audio.setSampleRate(new Integer((String) track.get("Sample Rate")).intValue());

                    }

                } catch (Exception ex) {

                    Tools.logException(PlaylistParser.class, ex);

                }

                if (track.containsKey("Comments")) {

                    audio.setComments((String) track.get("Comments"));

                }

                try {

                    if (track.containsKey("Play Count")) {

                        audio.setPlayCount(new Integer((String) track.get("Play Count")).intValue());

                    }

                } catch (Exception ex) {

                    Tools.logException(PlaylistParser.class, ex);

                }

                try {

                    if (track.containsKey("Play Date UTC")) {

                        audio.setDatePlayed(mDateFormat.parse((String) track.get("Play Date UTC")));

                    }

                } catch (Exception ex) {

                    Tools.logException(PlaylistParser.class, ex);

                }

                audio.setOrigen("iTunes");

                try {

                    if (audio.getId() == null) {

                        AudioManager.createAudio(audio);

                    }

                    else

                    {

                        AudioManager.updateAudio(audio);

                    }

                } catch (Exception ex) {

                    Tools.logException(PlaylistParser.class, ex);

                }

            }

        }

        private String decode(String value) {

            try {

                return Tools.unEscapeXMLChars(URLDecoder.decode(value, "UTF-8"));

            } catch (Exception ex) {

            }

            return value;

        }

        private SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");

        private boolean mFoundTracks;

        private boolean mFoundPlaylists;

        private int mDictLevel;

        private boolean mInKey;

        private StringBuffer mKey;

        private boolean mInValue;

        private String mLastTag;

        private String mType;

        private StringBuffer mValue;

        private HashMap mTracks;

        private int mCounter;

    }

    class ListParser extends DefaultHandler

    {

        public ListParser(List playlists)

        {

            mCurrentPlaylists = playlists;

            mKey = new StringBuffer(100);

            mValue = new StringBuffer(100);

        }

        /**
            
         * Method used by SAX at the start of the XML document parsing.
            
         */

        public void startDocument() {

        }

        /**
            
         * SAX XML parser method. Start of an element.
            
         * 
            
         * @param namespaceURI
            
         *            namespace URI this element is associated with, or an empty String
            
         * @param localName
            
         *            name of element (with no namespace prefix, if one is present)
            
         * @param qName
            
         *            XML 1.0 version of element name: [namespace prefix]:[localName]
            
         * @param attributes
            
         *            Attributes for this element
            
         */

        public void startElement(String namespaceURI, String localName, String qName, Attributes attributes)

                throws SAXException {

            if (localName.equals(DICT)) {

                mDictLevel = mDictLevel + 1;

            } else if (localName.equals(KEY)) {

                mInKey = true;

                mKey.setLength(0);

            }

            if (mLastTag != null && mLastTag.equals(KEY) && !localName.equals(KEY) && !localName.equals(DICT)) {

                mInValue = true;

                mValue.setLength(0);

                mType = localName;

            }

        }

        public void characters(char[] ch, int start, int length) throws SAXException {

            if (mInKey) {

                mKey.append(new String(ch, start, length).trim());

            } else if (mInValue) {

                mValue.append(new String(ch, start, length).trim());

            }

        }

        /**
            
         * SAX XML parser method. End of an element.
            
         * 
            
         * @param namespaceURI
            
         *            namespace URI this element is associated with, or an empty String
            
         * @param localName
            
         *            name of element (with no namespace prefix, if one is present)
            
         * @param qName
            
         *            XML 1.0 version of element name: [namespace prefix]:[localName]
            
         * @param attributes
            
         *            Attributes for this element
            
         */

        public void endElement(String namespaceURI, String localName, String qName) throws SAXException {

            mLastTag = localName;

            if (mDictLevel == 1 && localName.equals(KEY) && same(mKey, "Tracks")) {

                mFoundPlaylists = false;

            } else if (mDictLevel == 1 && localName.equals(KEY) && same(mKey, "Playlists")) {

                mFoundPlaylists = true;

            }

            if (localName.equals(DICT)) {

                mDictLevel = mDictLevel - 1;

            } else if (localName.equals(KEY)) {

                mInKey = false;

            } else if (mType != null && localName.equals(mType)) {

                if (mFoundPlaylists) {

                    if (mDictLevel == 2 && same(mKey, "Name")) {

                        if (mValue != null) {

                            mPlaylist = mValue.toString();

                        }

                    }

                    else

                    if (mDictLevel == 2 && mKey != null && same(mKey, "Playlist ID")) {

                        String value = mValue.toString();

                        mPlaylistId = value;

                        mCurrentPlaylists.add(value);

                        try {

                            boolean found = false;

                            List list = PlaylistsManager.findByExternalId(mPlaylistId);

                            if (list != null && list.size() > 0)

                            {

                                Playlists playlists = (Playlists) list.get(0);

                                PlaylistsManager.deletePlaylistsTracks(playlists);

                                found = true;

                                list.clear();

                            }

                            if (!found)

                            {

                                try {

                                    Playlists playlist = new Playlists(mPlaylist, new Date(), new Date(),
                                            new Date(), 0, "iTunes",

                                            mPlaylistId);

                                    PlaylistsManager.createPlaylists(playlist);

                                } catch (Exception ex) {

                                    Tools.logException(PlaylistParser.class, ex);

                                }

                            }

                            log.info("Processing Playlist: " + mPlaylist);

                        } catch (Exception ex) {

                            Tools.logException(PlaylistParser.class, ex);

                        }

                    }

                    else

                    if (mDictLevel == 3 && mKey != null && same(mKey, "Track ID")) {

                        try {

                            List list = AudioManager.findByExternalId(mValue.toString());

                            if (list != null && list.size() > 0) {

                                Audio audio = (Audio) list.get(0);

                                if (audio != null && mPlaylist != null) {

                                    if (!audio.getPath().startsWith("http"))

                                    {

                                        File file = new File(audio.getPath());

                                        if (!file.exists())

                                        {

                                            return;

                                        }

                                    }

                                    List plist = PlaylistsManager.findByExternalId(mPlaylistId);

                                    if (plist != null && plist.size() > 0)

                                    {

                                        Playlists playlists = (Playlists) plist.get(0);

                                        PlaylistsTracksManager.createPlaylistsTracks(
                                                new PlaylistsTracks(playlists.getId(), audio.getId()));

                                        plist.clear();

                                    }

                                    try {

                                        Thread.sleep(50); // give the CPU some breathing time

                                    } catch (Exception ex) {

                                    }

                                }

                                list.clear();

                            }

                        } catch (Exception ex) {

                            Tools.logException(PlaylistParser.class, ex);

                        }

                    }

                }

                mInValue = false;

            }

        }

        /**
            
         * Method used by SAX at the end of the XML document parsing.
            
         */

        public void endDocument() {

        }

        /**
            
         * Receive notification of a parser warning.
            
         * 
            
         * <p>
            
         * The default implementation does nothing. Application writers may override this method in a subclass to take
            
         * specific actions for each warning, such as inserting the message in a log file or printing it to the console.
            
         * </p>
            
         * 
            
         * @param e
            
         *            The warning information encoded as an exception.
            
         * @exception org.xml.sax.SAXException
            
         *                Any SAX exception, possibly wrapping another exception.
            
         * @see org.xml.sax.ErrorHandler#warning
            
         * @see org.xml.sax.SAXParseException
            
         */

        public void warning(SAXParseException e) throws SAXException {

            throw e;

        }

        /**
            
         * Receive notification of a recoverable parser error.
            
         * 
            
         * <p>
            
         * The default implementation does nothing. Application writers may override this method in a subclass to take
            
         * specific actions for each error, such as inserting the message in a log file or printing it to the console.
            
         * </p>
            
         * 
            
         * @param e
            
         *            The warning information encoded as an exception.
            
         * @exception org.xml.sax.SAXException
            
         *                Any SAX exception, possibly wrapping another exception.
            
         * @see org.xml.sax.ErrorHandler#warning
            
         * @see org.xml.sax.SAXParseException
            
         */

        public void error(SAXParseException e) throws SAXException {

            throw e;

        }

        /**
            
         * Report a fatal XML parsing error.
            
         * 
            
         * <p>
            
         * The default implementation throws a SAXParseException. Application writers may override this method in a subclass
            
         * if they need to take specific actions for each fatal error (such as collecting all of the errors into a single
            
         * report): in any case, the application must stop all regular processing when this method is invoked, since the
            
         * document is no longer reliable, and the parser may no longer report parsing events.
            
         * </p>
            
         * 
            
         * @param e
            
         *            The error information encoded as an exception.
            
         * @exception org.xml.sax.SAXException
            
         *                Any SAX exception, possibly wrapping another exception.
            
         * @see org.xml.sax.ErrorHandler#fatalError
            
         * @see org.xml.sax.SAXParseException
            
         */

        public void fatalError(SAXParseException e) throws SAXException {

            throw e;

        }

        private boolean mFoundPlaylists;

        private int mDictLevel;

        private boolean mInKey;

        private StringBuffer mKey;

        private boolean mInValue;

        private String mLastTag;

        private String mType;

        private StringBuffer mValue;

        private String mPlaylist;

        private String mPlaylistId;

        private List mCurrentPlaylists;

        private int mCounter;

    }

    private static boolean same(StringBuffer buffer, String value)

    {

        if (buffer.length() == value.length())

        {

            for (int i = 0; i < buffer.length(); i++)

            {

                if (value.charAt(i) != buffer.charAt(i))

                    return false;

            }

            return true;

        }

        return false;

    }

}