1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/utility/media_galleries/itunes_library_parser.h"
6
7#include <string>
8
9#include "base/logging.h"
10#include "base/strings/string16.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/strings/utf_string_conversions.h"
13#include "chrome/utility/media_galleries/iapps_xml_utils.h"
14#include "third_party/libxml/chromium/libxml_utils.h"
15#include "url/gurl.h"
16#include "url/url_canon.h"
17#include "url/url_util.h"
18
19namespace itunes {
20
21namespace {
22
23struct TrackInfo {
24  uint64 id;
25  base::FilePath location;
26  std::string artist;
27  std::string album;
28};
29
30class TrackInfoXmlDictReader : public iapps::XmlDictReader {
31 public:
32  TrackInfoXmlDictReader(XmlReader* reader, TrackInfo* track_info) :
33    iapps::XmlDictReader(reader), track_info_(track_info) {}
34
35  virtual bool ShouldLoop() OVERRIDE {
36    return !(Found("Track ID") && Found("Location") &&
37             Found("Album Artist") && Found("Album"));
38  }
39
40  virtual bool HandleKeyImpl(const std::string& key) OVERRIDE {
41    if (key == "Track ID") {
42      return iapps::ReadInteger(reader_, &track_info_->id);
43    } else if (key == "Location") {
44      std::string value;
45      if (!iapps::ReadString(reader_, &value))
46        return false;
47      GURL url(value);
48      if (!url.SchemeIsFile())
49        return false;
50      url::RawCanonOutputW<1024> decoded_location;
51      url::DecodeURLEscapeSequences(url.path().c_str() + 1,  // Strip /.
52                                    url.path().length() - 1,
53                                    &decoded_location);
54#if defined(OS_WIN)
55      base::string16 location(decoded_location.data(),
56                              decoded_location.length());
57#else
58      base::string16 location16(decoded_location.data(),
59                                decoded_location.length());
60      std::string location = "/" + base::UTF16ToUTF8(location16);
61#endif
62      track_info_->location = base::FilePath(location);
63    } else if (key == "Artist") {
64      if (Found("Album Artist"))
65        return false;
66      return iapps::ReadString(reader_, &track_info_->artist);
67    } else if (key == "Album Artist") {
68      track_info_->artist.clear();
69      return iapps::ReadString(reader_, &track_info_->artist);
70    } else if (key == "Album") {
71      return iapps::ReadString(reader_, &track_info_->album);
72    } else if (!SkipToNext()) {
73      return false;
74    }
75    return true;
76  }
77
78  virtual bool FinishedOk() OVERRIDE {
79    return Found("Track ID") && Found("Location");
80  }
81
82 private:
83  TrackInfo* track_info_;
84};
85
86// Walk through a dictionary filling in |result| with track information. Return
87// true if at least the id and location where found (artist and album may be
88// empty).  In either case, the cursor is advanced out of the dictionary.
89bool GetTrackInfoFromDict(XmlReader* reader, TrackInfo* result) {
90  DCHECK(result);
91  TrackInfoXmlDictReader dict_reader(reader, result);
92  return dict_reader.Read();
93}
94
95}  // namespace
96
97ITunesLibraryParser::ITunesLibraryParser() {}
98ITunesLibraryParser::~ITunesLibraryParser() {}
99
100bool ITunesLibraryParser::Parse(const std::string& library_xml) {
101  XmlReader reader;
102
103  if (!reader.Load(library_xml))
104    return false;
105
106  // Find the plist node and then search within that tag.
107  if (!iapps::SeekToNodeAtCurrentDepth(&reader, "plist"))
108    return false;
109  if (!reader.Read())
110    return false;
111
112  if (!iapps::SeekToNodeAtCurrentDepth(&reader, "dict"))
113    return false;
114
115  if (!iapps::SeekInDict(&reader, "Tracks"))
116    return false;
117
118  // Once inside the Tracks dict, we expect track dictionaries keyed by id. i.e.
119  //   <key>Tracks</key>
120  //   <dict>
121  //     <key>160</key>
122  //     <dict>
123  //       <key>Track ID</key><integer>160</integer>
124  if (!iapps::SeekToNodeAtCurrentDepth(&reader, "dict"))
125    return false;
126  int tracks_dict_depth = reader.Depth() + 1;
127  if (!reader.Read())
128    return false;
129
130  // Once parsing has gotten this far, return whatever is found, even if
131  // some of the data isn't extracted just right.
132  bool no_errors = true;
133  bool track_found = false;
134  while (reader.Depth() >= tracks_dict_depth) {
135    if (!iapps::SeekToNodeAtCurrentDepth(&reader, "key"))
136      return track_found;
137    std::string key;  // Should match track id below.
138    if (!reader.ReadElementContent(&key))
139      return track_found;
140    uint64 id;
141    bool id_valid = base::StringToUint64(key, &id);
142    if (!reader.SkipToElement())
143      return track_found;
144
145    TrackInfo track_info;
146    if (GetTrackInfoFromDict(&reader, &track_info) &&
147        id_valid &&
148        id == track_info.id) {
149      if (track_info.artist.empty())
150        track_info.artist = "Unknown Artist";
151      if (track_info.album.empty())
152        track_info.album = "Unknown Album";
153      parser::Track track(track_info.id, track_info.location);
154      library_[track_info.artist][track_info.album].insert(track);
155      track_found = true;
156    } else {
157      no_errors = false;
158    }
159  }
160
161  return track_found || no_errors;
162}
163
164}  // namespace itunes
165