Ticket #2: DbReader.java

File DbReader.java, 9.9 kB (added by morgan_guerin@yahoo.fr, 3 years ago)

DbReader? modified class

Line 
1 /*
2  * LastPod is an application used to publish one's iPod play counts to Last.fm.
3  * Copyright (C) 2007  muti, Chris Tilden
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  * package org.lastpod;
19  */
20 package org.lastpod;
21
22 import java.io.BufferedInputStream;
23 import java.io.DataInputStream;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27
28 import java.math.BigInteger;
29
30 import java.util.ArrayList;
31 import java.util.Calendar;
32 import java.util.Collections;
33 import java.util.List;
34
35 /**
36  * @author muti
37  * @version $Id$
38  */
39 public class DbReader {
40     private String itunesfile;
41     private String playcountsfile;
42     private BufferedInputStream itunesistream;
43     private BufferedInputStream playcountsistream;
44     private List history = null;
45     private ArrayList tracklist;
46     private ArrayList recentplays; //sorted by play time
47
48     /**
49      * Initializes the class with the locations of the iPod DB files.
50      *
51      * @param    itunespath    Directory containing the iTunesDB and the corresponding
52      *                         Play Counts, including trailing "\" or "/"
53      */
54     public DbReader(String itunespath) {
55         if (!itunespath.endsWith(File.separator)) {
56             itunespath += File.separator;
57         }
58
59         this.itunesfile = itunespath + "iTunesDB";
60         this.playcountsfile = itunespath + "Play Counts";
61         this.tracklist = new ArrayList();
62         this.recentplays = new ArrayList();
63     }
64
65     /**
66      * @return Returns recent plays.
67      */
68     public ArrayList getRecentplays() {
69         return this.recentplays;
70     }
71
72     /**
73      * Attempts to open and parse the DB & Play Counts files, creating
74      * the appropriate data structures.
75      *
76      * @throws IOException
77      */
78     public void parse() throws IOException {
79         try {
80             FileInputStream itstream = new FileInputStream(this.itunesfile);
81             this.itunesistream = new BufferedInputStream(itstream, 65535);
82         } catch (IOException e) {
83             throw new IOException("Error reading iTunes Database");
84         }
85
86         try {
87             FileInputStream pcstream = new FileInputStream(this.playcountsfile);
88             this.playcountsistream = new BufferedInputStream(pcstream, 65535);
89         } catch (IOException e) {
90             String errorMsg =
91                 "Error reading Play Counts Database.\n"
92                 + "Have you listened to any music on your iPod recently?\n"
93                 + "This can also be caused if you are running iTunes and you have it setup "
94                 + "to automatically run iTunes when an iPod is detected.";
95             throw new IOException(errorMsg);
96         }
97
98         this.parseitunesdb();
99         this.parseplaycounts();
100
101         this.itunesistream.close();
102         this.playcountsistream.close();
103     }
104
105     /**
106      * Parses track information from the iTunesDB
107      *
108      * @throws IOException
109      */
110     public void parseitunesdb() throws IOException {
111         byte[] buf = new byte[1];
112
113         //we seek one at a time because the mhit marker won't always be at a multiple of four
114         while (this.itunesistream.read(buf) != -1) {
115             if (buf[0] == 'm') { //Search for MHIT
116                 this.itunesistream.mark(1048576);
117                 buf = new byte[3];
118                 this.itunesistream.read(buf);
119
120                 if (new String(buf).equals("hit")) {
121                     this.tracklist.add(this.parsemhit());
122                 } else {
123                     this.itunesistream.reset();
124                 }
125             }
126
127             buf = new byte[1];
128         }
129     }
130
131     /**
132      * Parses an MHIT object from the iTunes Database
133      *
134      * @return Returns parsed track object.
135      * @throws IOException
136      */
137     public TrackItem parsemhit() throws IOException {
138         byte[] dword = new byte[4];
139         TrackItem track = new TrackItem();
140
141         this.itunesistream.mark(1048576); //mark beginning of MHIT location
142
143         this.itunesistream.read(dword);
144
145         long headersize = DbReader.LittleEndianToBigInt(dword).longValue();
146
147         DbReader.SkipFully(this.itunesistream, 4);
148         this.itunesistream.read(dword);
149
150         long nummhods = DbReader.LittleEndianToBigInt(dword).longValue();
151
152         this.itunesistream.read(dword);
153         track.setTrackid(DbReader.LittleEndianToBigInt(dword).longValue());
154
155         DbReader.SkipFully(this.itunesistream, 20);
156         this.itunesistream.read(dword);
157         track.setLength(DbReader.LittleEndianToBigInt(dword).longValue() / 1000);
158
159         this.itunesistream.reset();
160         DbReader.SkipFully(this.itunesistream, headersize - 4); //skip to end of MHIT
161
162         for (long i = 0; i < nummhods; i++) {
163             this.parsemhod(track);
164         }
165        
166         return track;
167     }
168
169     /**
170      * Parses an MHOD object and sets proper fields in the track item object
171      *
172      * @param    TrackItem    Track Item
173      * @throws IOException
174      */
175     public void parsemhod(TrackItem track) throws IOException {
176         byte[] dword = new byte[4];
177
178         this.itunesistream.mark(1048576); //mark beginning of MHOD location
179
180         DbReader.SkipFully(this.itunesistream, 8);
181
182         this.itunesistream.read(dword);
183
184         long totalsize = DbReader.LittleEndianToBigInt(dword).longValue();
185
186         this.itunesistream.read(dword);
187
188         int mhodtype = DbReader.LittleEndianToBigInt(dword).intValue();
189
190         if ((mhodtype == 1) || (mhodtype == 3) || (mhodtype == 4)) {
191             DbReader.SkipFully(this.itunesistream, 12);
192             this.itunesistream.read(dword);
193
194             int strlen = DbReader.LittleEndianToBigInt(dword).intValue();
195
196             DbReader.SkipFully(this.itunesistream, 8);
197
198             byte[] data = new byte[strlen];
199             this.itunesistream.read(data);
200
201             String stringdata = new String(data, "UTF-16LE");
202
203             switch (mhodtype) {
204             case 1:
205                 track.setTrack(stringdata);
206
207                 break;
208
209             case 3:
210                 track.setAlbum(stringdata);
211
212                 break;
213
214             case 4:
215                 track.setArtist(stringdata);
216
217                 break;
218             }
219         }
220
221         this.itunesistream.reset();
222         DbReader.SkipFully(this.itunesistream, totalsize);
223     }
224
225     /**
226      * Parses play counts information from "Play Counts"
227      *
228      * @throws IOException
229      */
230     public void parseplaycounts() throws IOException {
231         byte[] dword = new byte[4];
232
233         DbReader.SkipFully(this.playcountsistream, 8);
234         this.playcountsistream.read(dword);
235
236         long entrylen = DbReader.LittleEndianToBigInt(dword).longValue();
237
238         this.playcountsistream.read(dword);
239
240         int numentries = DbReader.LittleEndianToBigInt(dword).intValue();
241
242         DbReader.SkipFully(this.playcountsistream, 80); //skip rest of header
243
244         for (int i = 0; i < (numentries - 1); i++) {
245             this.playcountsistream.mark(1048576); //save beginning of entry location
246
247             this.playcountsistream.read(dword);
248
249             long playcount = DbReader.LittleEndianToBigInt(dword).longValue();
250
251             if (playcount > 0) {
252                 this.playcountsistream.read(dword);
253
254                 long lastplayed = DbReader.LittleEndianToBigInt(dword).longValue();
255                 lastplayed -= 2082844800; //convert to UNIX timestamp
256
257                 Calendar calendar = Calendar.getInstance();
258                 long offset =
259                     calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
260                 lastplayed -= (offset / 1000);
261
262                 TrackItem temptrack = (TrackItem) this.tracklist.get(i);
263                 temptrack.setPlaycount(playcount);
264                 temptrack.setLastplayed(lastplayed - temptrack.getLength());
265                 if (History.getInstance().isInHistory(temptrack.getLastplayed())){
266                         temptrack.setActive(Boolean.FALSE);
267                 }
268                 this.recentplays.add(this.tracklist.get(i));
269             }
270
271             this.playcountsistream.reset();
272             DbReader.SkipFully(this.playcountsistream, entrylen);
273         }
274
275         Collections.sort(this.recentplays);
276     }
277
278     /**
279      * This converts any size byte array to a BigInteger
280      *
281      * @param Little-Endian byte array
282      * @return BigInt
283      */
284     public static BigInteger LittleEndianToBigInt(byte[] num) {
285         byte temp;
286
287         int upperBound = num.length - 1;
288         int lowerBound = 0;
289
290         while (lowerBound < upperBound) {
291             temp = num[lowerBound];
292             num[lowerBound] = num[upperBound];
293             num[upperBound] = temp;
294             lowerBound++;
295             upperBound--;
296         }
297
298         return new BigInteger(1, num);
299     }
300
301     /**
302      * Guarantees that the specified number of bytes will be skipped
303      *
304      * @param Input Stream
305      * @param Number of bytes to skip
306      * @throws IOException
307      */
308     public static void SkipFully(BufferedInputStream stream, long bytes)
309             throws IOException {
310         for (long i = stream.skip(bytes); i < bytes; i += stream.skip(bytes - i)) {
311         }
312     }
313    
314  
315    
316    
317 }