1/*
2   Project: MPDCon
3
4   Copyright (C) 2004
5
6   Author: Daniel Luederwald
7
8   Created: 2004-05-14 11:53:40 +0200 by flip
9
10   PlaylistItem
11
12   This application is free software; you can redistribute it and/or
13   modify it under the terms of the GNU General Public
14   License as published by the Free Software Foundation; either
15   version 2 of the License, or (at your option) any later version.
16
17   This application is distributed in the hope that it will be useful,
18   but WITHOUT ANY WARRANTY; without even the implied warranty of
19   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20   Library General Public License for more details.
21
22   You should have received a copy of the GNU General Public
23   License along with this library; if not, write to the Free
24   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
25*/
26
27#include "PlaylistItem.h"
28
29/* ----------------------
30   - Private Categories -
31   ----------------------*/
32// inspired/stolen from SOPE
33@interface NSObject(StringBindings)
34- (NSString *)valueForStringBinding:(NSString *)_key;
35@end
36
37@interface NSString(misc)
38- (NSString *)stringByReplacingVariablesWithBindings:(id)_bindings;
39@end
40
41@implementation PlaylistItem
42
43// MPD itself doesn't provide the ability to retrieve lyrics via the server
44// have to use external services to provide that ability
45static NSString *LyricsAPIURL=@"http://lyrics.wikia.com/api.php?func=getSong&artist=$ARTIST$&song=$TITLE$&fmt=xml";
46
47/* --------------------------
48   - Initialization Methods -
49   --------------------------*/
50
51- (id) init
52{
53  MPDConDB = [SQLiteAdaptor sharedSQLiteAdaptor];
54  return self;
55}
56
57- (void) dealloc
58{
59  [artist release];
60  [title release];
61  [album release];
62  [genre release];
63  [trackNr release];
64  [path release];
65  [comment release];
66  [composer release];
67  [date release];
68  [performer release];
69  [disc release];
70  [lyricsText release];
71  [lyricsURL release];
72
73  [super dealloc];
74}
75
76/* --------------------
77   - Accessor Methods -
78   --------------------*/
79
80- (NSString *) getArtist
81{
82  return artist;
83}
84
85- (void) setArtist: (NSString *)newArtist
86{
87  [artist autorelease];
88  artist = [newArtist copy];
89}
90
91- (NSString *) getAlbum
92{
93  return album;
94}
95
96- (void) setAlbum: (NSString *)newAlbum
97{
98  [album autorelease];
99  album = [newAlbum copy];
100}
101
102- (NSString *) getGenre
103{
104  return genre;
105}
106
107- (void) setGenre: (NSString *)newGenre
108{
109  [genre autorelease];
110  genre = [newGenre copy];
111}
112
113- (NSString *) getTitle
114{
115  return title;
116}
117
118- (void) setTitle: (NSString *)newTitle
119{
120  [title autorelease];
121  title = [newTitle copy];
122}
123
124- (NSString *) getTrackNr
125{
126  return trackNr;
127}
128
129- (void) setTrackNr: (NSString *)newNr
130{
131  [trackNr autorelease];
132  trackNr = [newNr copy];
133}
134
135- (NSString *) getComment
136{
137  return comment;
138}
139
140- (void) setComment: (NSString *)newComment
141{
142  [comment autorelease];
143  comment = [newComment copy];
144}
145
146- (NSString *) getComposer
147{
148  return composer;
149}
150
151- (void) setComposer: (NSString *)newComposer
152{
153  [composer autorelease];
154  composer = [newComposer copy];
155}
156
157- (NSString *) getDate
158{
159  return date;
160}
161
162- (void) setDate: (NSString *)newDate
163{
164  [date autorelease];
165  date = [newDate copy];
166}
167
168- (NSString *) getPerformer
169{
170  return performer;
171}
172
173- (void) setPerformer: (NSString *)newPerformer
174{
175  [performer autorelease];
176  performer = [newPerformer copy];
177}
178
179- (NSString *) getDisc
180{
181  return disc;
182}
183
184- (void) setDisc: (NSString *)newDisc
185{
186  [disc autorelease];
187  disc = [newDisc copy];
188}
189
190- (int) getElapsedTime
191{
192  return elapsedTime;
193}
194
195- (void) setElapsedTime: (int)newTime
196{
197  elapsedTime = newTime;
198}
199
200- (int) getTotalTime
201{
202  return totalTime;
203}
204
205- (void) setTotalTime: (int)newTime
206{
207  totalTime = newTime;
208}
209
210- (NSString *) getPath
211{
212  return path;
213}
214
215- (void) setPath: (NSString *)newPath
216{
217  [path autorelease];
218  path = [newPath copy];
219}
220
221- (NSInteger) getRating
222{
223  return [MPDConDB getRatingForFile:path];
224}
225
226- (void) setRating: (NSInteger)newRating
227{
228  [MPDConDB setRating: newRating forFile: path];
229}
230
231- (NSDictionary *) getLyrics
232{
233  NSDictionary *lyricsDict;
234  NSDictionary *bindings;
235  NSString *requestURL;
236  NSData *result;
237  NSURL *url;
238  NSXMLParser *parser;
239
240  if ([[self getArtist] isEqual:@"Unknown Artist"] || [[self getTitle] isEqual:@"Unknown Title"])
241    {
242      lyricsText = [@"No lyrics retrieved for unknown artist or song." copy];
243      lyricsURL = [@"http://lyrics.wikia.com" copy];
244      lyricsDict =
245    	[[NSDictionary alloc] initWithObjectsAndKeys:
246      	  lyricsText, @"lyricsText",
247      	  lyricsURL, @"lyricsURL",
248      	  nil];
249      return [lyricsDict autorelease];
250    }
251
252  lyricsDict = [MPDConDB getLyricsForFile:path];
253  if (lyricsDict)
254    return lyricsDict;
255
256  bindings =
257    [[NSDictionary alloc] initWithObjectsAndKeys:
258    [[self getArtist] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], @"ARTIST",
259    [[self getTitle] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], @"TITLE",
260      nil];
261  requestURL = [LyricsAPIURL stringByReplacingVariablesWithBindings:bindings];
262  RELEASE(bindings);
263
264  url = [NSURL URLWithString:requestURL];
265  result = [NSData dataWithContentsOfURL:url];
266  if (result)
267    {
268      parser = [[[NSXMLParser alloc] initWithData:result] autorelease];
269      [parser setShouldProcessNamespaces:NO];
270      [parser setShouldReportNamespacePrefixes:NO];
271      [parser setShouldResolveExternalEntities:NO];
272      [parser setDelegate:self];
273      [parser parse];
274      // if we got something from the web, we save it in the DB
275      if (![lyricsText isEqual:@"Not found"])
276        {
277          [self setLyrics: lyricsText withURL: lyricsURL];
278        }
279    }
280  else
281    {
282      lyricsText = [@"unable to connect to lyrics.wikia.com" copy];
283      lyricsURL = [@"http://lyrics.wikia.com" copy];
284    }
285
286  lyricsDict =
287    [[NSDictionary alloc] initWithObjectsAndKeys:
288      lyricsText, @"lyricsText",
289      lyricsURL, @"lyricsURL",
290      nil];
291
292  return [lyricsDict autorelease];
293}
294- (void) setLyrics: (NSString *) _lyricsText withURL: (NSString *) _lyricsURL
295{
296  [MPDConDB setLyrics: _lyricsText withURL: _lyricsURL forFile: path];
297}
298
299- (void) setID: (int) newID
300{
301  ID = newID;
302}
303
304- (int) getID
305{
306  return ID;
307}
308
309- (void) setPos: (int) newPos
310{
311  pos = newPos;
312}
313
314- (int) getPos
315{
316  return pos;
317}
318
319/* getLyrics NSXMLParser delegate methods */
320- (void) parser:(NSXMLParser *)parser
321didStartElement:(NSString *)elementName
322   namespaceURI:(NSString *)namespaceURI
323  qualifiedName:(NSString *)qualifiedName
324     attributes:(NSDictionary *)attributeDict
325{
326  element = [NSMutableString string];
327}
328
329- (void) parser:(NSXMLParser *)parser
330  didEndElement:(NSString *)elementName
331   namespaceURI:(NSString *)namespaceURI
332  qualifiedName:(NSString *)qName
333{
334  if ([elementName isEqualToString:@"lyrics"])
335    {
336      lyricsText = [element copy];
337    }
338  else if ([elementName isEqualToString:@"url"])
339    {
340      lyricsURL = [element copy];
341    }
342}
343
344- (void) parser:(NSXMLParser *)parser
345foundCharacters:(NSString *)string
346{
347 if(element == nil)
348  {
349    element = [[NSMutableString alloc] init];
350  }
351
352 [element appendString:string];
353}
354@end
355
356/* ----------------------
357   - Private Categories -
358   ---------------------*/
359
360@implementation NSObject(StringBindings)
361- (NSString *)valueForStringBinding:(NSString *)_key {
362  if (_key == nil) return nil;
363  return [self valueForKeyPath:_key];
364}
365@end
366
367@implementation NSString(misc)
368- (NSString *)stringByReplacingVariablesWithBindings:(id)_bindings
369{
370  NSUInteger      len, pos = 0;
371  unichar         *wbuf    = NULL;
372  NSMutableString *str     = nil;
373
374  str = [self mutableCopy];
375  len = [str length];
376  wbuf   = malloc(sizeof(unichar) * (len + 4));
377  [self getCharacters:wbuf];
378
379  while (pos < len)
380    {
381      if (pos + 1 == len) /* last entry */
382        {
383          if (wbuf[pos] == '$') /* found $ without end-char */
384            {
385              [NSException raise:@"NSStringVariableBindingException"
386                 format:@"did not find end of variable for string %@", self];
387            }
388          break;
389        }
390      if (wbuf[pos] == '$')
391        {
392          if (wbuf[pos + 1] == '$')/* found $$ --> $ */
393            {
394              [str deleteCharactersInRange:NSMakeRange(pos, 1)];
395
396              if (wbuf != NULL)
397                {
398                  free(wbuf); wbuf = NULL;
399                }
400              len  = [str length];
401              wbuf = malloc(sizeof(unichar) * (len + 4));
402              [str getCharacters:wbuf];
403            }
404          else
405            {
406              NSUInteger startPos = pos;
407
408              pos += 2; /* wbuf[pos + 1] != '$' */
409              while (pos < len)
410                {
411                  if (wbuf[pos] != '$')
412                    pos++;
413                  else
414                    break;
415                }
416              if (pos == len) /* end of string was reached */
417                {
418                  [NSException raise:@"NSStringVariableBindingException"
419                     format:@"did not find end of variable for string %@",
420                     self];
421                }
422              else
423                {
424                  NSString *key;
425                  NSString *value;
426
427                  key = [[NSString alloc]
428                          initWithCharacters:(wbuf + startPos + 1)
429                          length:(pos - startPos - 1)];
430
431                  if ((value = [_bindings valueForStringBinding:key]) == nil)
432                    {
433                      value = @"";
434                    }
435                  [key release]; key = nil;
436
437                  [str replaceCharactersInRange:
438                         NSMakeRange(startPos, pos - startPos + 1)
439                       withString:value];
440
441                  if (wbuf != NULL)
442                    {
443                      free(wbuf); wbuf = NULL;
444                    }
445                  len  = [str length];
446                  wbuf = malloc(sizeof(unichar) * (len + 4));
447                  [str getCharacters:wbuf];
448
449                  pos = startPos - 1 + [value length];
450                }
451            }
452        }
453      pos++;
454    }
455  if (wbuf != NULL)
456    {
457      free(wbuf); wbuf = NULL;
458    }
459  {
460    id tmp = str;
461    str = [str copy];
462    [tmp release]; tmp = nil;
463  }
464  return [str autorelease];
465}
466@end /* NSString(misc) */
467