itunes_library_parser.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
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
30// Walk through a dictionary filling in |result| with track information. Return
31// true if at least the id and location where found (artist and album may be
32// empty).  In either case, the cursor is advanced out of the dictionary.
33bool GetTrackInfoFromDict(XmlReader* reader, TrackInfo* result) {
34  DCHECK(result);
35  if (reader->NodeName() != "dict")
36    return false;
37
38  int dict_content_depth = reader->Depth() + 1;
39  // Advance past the dict node and into the body of the dictionary.
40  if (!reader->Read())
41    return false;
42
43  bool found_id = false;
44  bool found_location = false;
45  bool found_artist = false;
46  bool found_album_artist = false;
47  bool found_album = false;
48  while (reader->Depth() >= dict_content_depth &&
49         !(found_id && found_location && found_album_artist && found_album)) {
50    if (!iapps::SeekToNodeAtCurrentDepth(reader, "key"))
51      break;
52    std::string found_key;
53    if (!reader->ReadElementContent(&found_key))
54      break;
55    DCHECK_EQ(dict_content_depth, reader->Depth());
56
57    if (found_key == "Track ID") {
58      if (found_id)
59        break;
60      if (!iapps::ReadInteger(reader, &result->id))
61        break;
62      found_id = true;
63    } else if (found_key == "Location") {
64      if (found_location)
65        break;
66      std::string value;
67      if (!iapps::ReadString(reader, &value))
68        break;
69      GURL url(value);
70      if (!url.SchemeIsFile())
71        break;
72      url_canon::RawCanonOutputW<1024> decoded_location;
73      url_util::DecodeURLEscapeSequences(url.path().c_str() + 1,  // Strip /.
74                                         url.path().length() - 1,
75                                         &decoded_location);
76#if defined(OS_WIN)
77      string16 location(decoded_location.data(), decoded_location.length());
78#else
79      string16 location16(decoded_location.data(), decoded_location.length());
80      std::string location = "/" + UTF16ToUTF8(location16);
81#endif
82      result->location = base::FilePath(location);
83      found_location = true;
84    } else if (found_key == "Artist") {
85      if (found_artist || found_album_artist)
86        break;
87      if (!iapps::ReadString(reader, &result->artist))
88        break;
89      found_artist = true;
90    } else if (found_key == "Album Artist") {
91      if (found_album_artist)
92        break;
93      result->artist.clear();
94      if (!iapps::ReadString(reader, &result->artist))
95        break;
96      found_album_artist = true;
97    } else if (found_key == "Album") {
98      if (found_album)
99        break;
100      if (!iapps::ReadString(reader, &result->album))
101        break;
102      found_album = true;
103    } else {
104      if (!iapps::SkipToNextElement(reader))
105        break;
106      if (!reader->Next())
107        break;
108    }
109  }
110
111  // Seek to the end of the dictionary
112  while (reader->Depth() >= dict_content_depth)
113    reader->Next();
114
115  return found_id && found_location;
116}
117
118}  // namespace
119
120ITunesLibraryParser::ITunesLibraryParser() {}
121ITunesLibraryParser::~ITunesLibraryParser() {}
122
123bool ITunesLibraryParser::Parse(const std::string& library_xml) {
124  XmlReader reader;
125
126  if (!reader.Load(library_xml))
127    return false;
128
129  // Find the plist node and then search within that tag.
130  if (!iapps::SeekToNodeAtCurrentDepth(&reader, "plist"))
131    return false;
132  if (!reader.Read())
133    return false;
134
135  if (!iapps::SeekToNodeAtCurrentDepth(&reader, "dict"))
136    return false;
137
138  if (!iapps::SeekInDict(&reader, "Tracks"))
139    return false;
140
141  // Once inside the Tracks dict, we expect track dictionaries keyed by id. i.e.
142  //   <key>Tracks</key>
143  //   <dict>
144  //     <key>160</key>
145  //     <dict>
146  //       <key>Track ID</key><integer>160</integer>
147  if (!iapps::SeekToNodeAtCurrentDepth(&reader, "dict"))
148    return false;
149  int tracks_dict_depth = reader.Depth() + 1;
150  if (!reader.Read())
151    return false;
152
153  // Once parsing has gotten this far, return whatever is found, even if
154  // some of the data isn't extracted just right.
155  bool no_errors = true;
156  bool track_found = false;
157  while (reader.Depth() >= tracks_dict_depth) {
158    if (!iapps::SeekToNodeAtCurrentDepth(&reader, "key"))
159      return track_found;
160    std::string key;  // Should match track id below.
161    if (!reader.ReadElementContent(&key))
162      return track_found;
163    uint64 id;
164    bool id_valid = base::StringToUint64(key, &id);
165    if (!reader.SkipToElement())
166      return track_found;
167
168    TrackInfo track_info;
169    if (GetTrackInfoFromDict(&reader, &track_info) &&
170        id_valid &&
171        id == track_info.id) {
172      if (track_info.artist.empty())
173        track_info.artist = "Unknown Artist";
174      if (track_info.album.empty())
175        track_info.album = "Unknown Album";
176      parser::Track track(track_info.id, track_info.location);
177      library_[track_info.artist][track_info.album].insert(track);
178      track_found = true;
179    } else {
180      no_errors = false;
181    }
182  }
183
184  return track_found || no_errors;
185}
186
187}  // namespace itunes
188