Index: /trunk/src/main/org/lastpod/parser/ItunesStatsParser.java =================================================================== --- /trunk/src/main/org/lastpod/parser/ItunesStatsParser.java (revision 91) +++ /trunk/src/main/org/lastpod/parser/ItunesStatsParser.java (revision 93) @@ -22,4 +22,5 @@ import org.lastpod.util.IoUtils; +import org.lastpod.util.ItunesStatsFilter; import java.io.BufferedInputStream; @@ -113,5 +114,7 @@ playCountsBufferedIn = new BufferedInputStream(playCountsFileIn, 65535); - return parseitunesStats(playCountsBufferedIn); + List trackList = parseitunesStats(playCountsBufferedIn); + + return manufactureLastPlayed(trackList); } catch (IOException e) { String errorMsg = @@ -145,6 +148,4 @@ IoUtils.skipFully(itunesStatsistream, 3); //skip rest of header - Calendar calendar = Calendar.getInstance(); - for (int i = 0; i < (numentries - 1); i++) { itunesStatsistream.mark(1048576); //save beginning of entry location @@ -164,7 +165,4 @@ TrackItem temptrack = (TrackItem) trackList.get(i); temptrack.setPlaycount(playcount); - calendar.add(Calendar.SECOND, -(int) temptrack.getLength()); - temptrack.setLastplayed(calendar.getTimeInMillis() / 1000); - recentPlays.add(trackList.get(i)); @@ -173,5 +171,5 @@ for (long j = 0; j < numberToManufacture; j++) { - temptrack = manufactureTrack(temptrack, calendar); + temptrack = manufactureTrack(temptrack); recentPlays.add(temptrack); } @@ -193,9 +191,6 @@ * @return A manufactured TrackItem. */ - private TrackItem manufactureTrack(TrackItem temptrack, Calendar calendar) { + private TrackItem manufactureTrack(TrackItem temptrack) { TrackItem manufacturedTrack = new TrackItem(temptrack); - calendar.add(Calendar.SECOND, -(int) manufacturedTrack.getLength()); - manufacturedTrack.setLastplayed(calendar.getTimeInMillis() / 1000); - manufacturedTrack.setPlaycount(1); temptrack.setPlaycount(1); @@ -203,3 +198,35 @@ return manufacturedTrack; } + + /** + * Manufactures the last played times for all the track items. + * @param trackItems The list of track items to modify. + * @return The modified list of track items. + */ + private List manufactureLastPlayed(List trackItems) { + Calendar calendar = Calendar.getInstance(); + TrackItem temptrack = null; + + for (int i = trackItems.size() - 1; i >= 0; i--) { + temptrack = (TrackItem) trackItems.get(i); + calendar.add(Calendar.SECOND, -(int) temptrack.getLength()); + temptrack.setLastplayed(calendar.getTimeInMillis() / 1000); + } + + return trackItems; + } + + /** + * Utility function to determine if the iTunesPath is that of an iPod shuffle. + * @param iTunesPath The path to the iTunes_Control directory. + * @return true if the iPod is a Shuffle. + */ + public static boolean isIpodShuffle(String iTunesPath) { + /* Checks for the "iTunesStats" file. If it exists, switch to the iPod + * shuffle file. */ + File file = new File(iTunesPath); + File[] itunesStatsFiles = file.listFiles(new ItunesStatsFilter()); + + return (itunesStatsFiles != null) && (itunesStatsFiles.length != 0); + } } Index: /trunk/src/main/org/lastpod/parser/ItunesDbParser.java =================================================================== --- /trunk/src/main/org/lastpod/parser/ItunesDbParser.java (revision 92) +++ /trunk/src/main/org/lastpod/parser/ItunesDbParser.java (revision 93) @@ -29,6 +29,10 @@ import java.io.InputStream; +import java.math.BigInteger; + import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** @@ -45,4 +49,9 @@ /** + * The location of the iTunesSD file. + */ + private String iTunesSdFile; + + /** * Stores a boolean value that will be passed into TrackItem. */ @@ -53,4 +62,9 @@ */ String[] variousArtistsStrings; + + /** + * Stores true if this iTunesDB is for an iPod shuffle. + */ + boolean isShuffle = false; /** @@ -70,7 +84,8 @@ * @param variousArtistsStrings A String array containing the various artist * strings that should be parsed. + * @param isShuffle true if this iTunesDB is for an iPod shuffle. */ public ItunesDbParser(String iTunesPath, boolean parseVariousArtists, - String[] variousArtistsStrings) { + String[] variousArtistsStrings, boolean isShuffle) { if (!iTunesPath.endsWith(File.separator)) { iTunesPath += File.separator; @@ -78,6 +93,8 @@ this.iTunesFile = iTunesPath + "iTunesDB"; + this.iTunesSdFile = iTunesPath + "iTunesSD"; this.parseVariousArtists = parseVariousArtists; this.variousArtistsStrings = variousArtistsStrings; + this.isShuffle = isShuffle; } @@ -95,5 +112,11 @@ itunesBufferedIn = new BufferedInputStream(itunesFileIn, 65535); - return parseitunesdb(itunesBufferedIn); + List trackList = parseitunesdb(itunesBufferedIn); + + if (!isShuffle) { + return trackList; + } else { + return readItunesSdAndReorderList(trackList); + } } catch (IOException e) { throw new RuntimeException("Error reading iTunes Database"); @@ -240,4 +263,69 @@ /** + * Reads the iTunesSD file and uses the ordering in this file to reorder + * the track list. + * @param trackList The track list from the iTunesDB + * @return A new java.util.List that is ordered per the + * iTunesSD order. + * @throws IOException Thrown if I/O errors occur. + */ + private List readItunesSdAndReorderList(List trackList) + throws IOException { + InputStream itunesSdFileIn = null; + InputStream itunesSdBufferedIn = null; + + try { + itunesSdFileIn = new FileInputStream(iTunesSdFile); + itunesSdBufferedIn = new BufferedInputStream(itunesSdFileIn, 65535); + + /* Converts the trackList into a Map. */ + Map trackMap = new HashMap(); + TrackItem track = null; + + for (int i = 0; i < trackList.size(); i++) { + track = (TrackItem) trackList.get(i); + trackMap.put(track.getLocation(), track); + } + + List orderedTrackList = new ArrayList(); + + byte[] threeBytes = new byte[3]; + + itunesSdBufferedIn.read(threeBytes); + + int numentries = (new BigInteger(threeBytes)).intValue(); + + IoUtils.skipFully(itunesSdBufferedIn, 15); //skip rest of header + assert (numentries == trackList.size()); + + for (int i = 0; i < (numentries - 1); i++) { + itunesSdBufferedIn.mark(1048576); //save beginning of entry location + + itunesSdBufferedIn.read(threeBytes); + + int entrylen = (new BigInteger(threeBytes)).intValue(); + + IoUtils.skipFully(itunesSdBufferedIn, 30); + + byte[] data = new byte[522]; + itunesSdBufferedIn.read(data); + + /* Filename should have : characters instead of / characters. */ + String filename = new String(data, "UTF-16LE").replace('/', ':').trim(); + + orderedTrackList.add(trackMap.get(filename)); + + itunesSdBufferedIn.reset(); + IoUtils.skipFully(itunesSdBufferedIn, entrylen); + } + + return orderedTrackList; + } finally { + IoUtils.cleanup(itunesSdFileIn, null); + IoUtils.cleanup(itunesSdBufferedIn, null); + } + } + + /** * Does nothing for this implementation. * @param trackList Does nothing for this implementation. Index: /trunk/src/main/org/lastpod/ModelImpl.java =================================================================== --- /trunk/src/main/org/lastpod/ModelImpl.java (revision 90) +++ /trunk/src/main/org/lastpod/ModelImpl.java (revision 93) @@ -24,8 +24,5 @@ import org.lastpod.parser.TrackItemParser; -import org.lastpod.util.ItunesStatsFilter; import org.lastpod.util.MiscUtilities; - -import java.io.File; import java.util.ArrayList; @@ -101,17 +98,17 @@ History.getInstance(iTunesPath); + boolean isShuffle = ItunesStatsParser.isIpodShuffle(iTunesPath); + ItunesDbParser itunesDbParser = - new ItunesDbParser(iTunesPath, parseVariousArtists, splitVariousArtistStrings); - - /* Defaults to the parser for non-shuffle iPods. */ - TrackItemParser playCountsParser = new PlayCountsParser(iTunesPath, parseMultiPlayTracks); - - /* Checks for the "iTunesStats" file. If it exists, switch to the iPod - * shuffle parser. */ - File file = new File(iTunesPath); - File[] itunesStatsFiles = file.listFiles(new ItunesStatsFilter()); - - if ((itunesStatsFiles != null) && (itunesStatsFiles.length != 0)) { + new ItunesDbParser(iTunesPath, parseVariousArtists, splitVariousArtistStrings, isShuffle); + + TrackItemParser playCountsParser = null; + + /* If the iPod is a Shuffle, use the iPod shuffle parser. Otherwise + * use the non-shuffle parser. */ + if (isShuffle) { playCountsParser = new ItunesStatsParser(iTunesPath, parseMultiPlayTracks); + } else { + playCountsParser = new PlayCountsParser(iTunesPath, parseMultiPlayTracks); } Index: /trunk/src/main/org/lastpod/action/DeletePlayCounts.java =================================================================== --- /trunk/src/main/org/lastpod/action/DeletePlayCounts.java (revision 89) +++ /trunk/src/main/org/lastpod/action/DeletePlayCounts.java (revision 93) @@ -22,5 +22,5 @@ import org.lastpod.UI; -import org.lastpod.util.ItunesStatsFilter; +import org.lastpod.parser.ItunesStatsParser; import java.awt.event.ActionEvent; @@ -91,10 +91,6 @@ playCountsFile = new File(iTunesPath + "Play Counts"); - /* Checks for the "iTunesStats" file. If it exists, switch to the iPod - * shuffle file. */ - File file = new File(iTunesPath); - File[] itunesStatsFiles = file.listFiles(new ItunesStatsFilter()); - - if ((itunesStatsFiles != null) && (itunesStatsFiles.length != 0)) { + /* If the iPod is a Shuffle, switch to the shuffle delete logic. */ + if (ItunesStatsParser.isIpodShuffle(iTunesPath)) { playCountsFile = new File(iTunesPath + "iTunesStats"); putValue(SHORT_DESCRIPTION, "Removes the iTunesStats file from the iPod shuffle.");