1868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved.
2868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// found in the LICENSE file.
4868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
5a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)#include "chrome/utility/media_galleries/itunes_library_parser.h"
6868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
7868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include <string>
8868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
9868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/logging.h"
10868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/string16.h"
11868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/string_number_conversions.h"
12868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/utf_string_conversions.h"
138bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/utility/media_galleries/iapps_xml_utils.h"
14868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "third_party/libxml/chromium/libxml_utils.h"
157dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "url/gurl.h"
167dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "url/url_canon.h"
177dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "url/url_util.h"
18868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
19868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)namespace itunes {
20868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
21868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)namespace {
22868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
23868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)struct TrackInfo {
24868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  uint64 id;
25868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  base::FilePath location;
26868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  std::string artist;
27868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  std::string album;
28868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)};
29868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class TrackInfoXmlDictReader : public iapps::XmlDictReader {
315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) public:
325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  TrackInfoXmlDictReader(XmlReader* reader, TrackInfo* track_info) :
335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    iapps::XmlDictReader(reader), track_info_(track_info) {}
34868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  virtual bool ShouldLoop() OVERRIDE {
365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return !(Found("Track ID") && Found("Location") &&
375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)             Found("Album Artist") && Found("Album"));
385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  }
39868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  virtual bool HandleKeyImpl(const std::string& key) OVERRIDE {
415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if (key == "Track ID") {
425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      return iapps::ReadInteger(reader_, &track_info_->id);
435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    } else if (key == "Location") {
44868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      std::string value;
455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      if (!iapps::ReadString(reader_, &value))
465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return false;
47868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      GURL url(value);
48868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      if (!url.SchemeIsFile())
495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return false;
505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      url::RawCanonOutputW<1024> decoded_location;
515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      url::DecodeURLEscapeSequences(url.path().c_str() + 1,  // Strip /.
525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                                    url.path().length() - 1,
535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                                    &decoded_location);
54868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#if defined(OS_WIN)
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      base::string16 location(decoded_location.data(),
565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                              decoded_location.length());
57868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#else
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      base::string16 location16(decoded_location.data(),
595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                decoded_location.length());
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      std::string location = "/" + base::UTF16ToUTF8(location16);
61868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#endif
625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      track_info_->location = base::FilePath(location);
635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    } else if (key == "Artist") {
645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      if (Found("Album Artist"))
655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return false;
665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      return iapps::ReadString(reader_, &track_info_->artist);
675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    } else if (key == "Album Artist") {
685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      track_info_->artist.clear();
695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      return iapps::ReadString(reader_, &track_info_->artist);
705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    } else if (key == "Album") {
715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      return iapps::ReadString(reader_, &track_info_->album);
725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    } else if (!SkipToNext()) {
735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      return false;
74868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    }
755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return true;
76868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  }
77868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  virtual bool FinishedOk() OVERRIDE {
795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return Found("Track ID") && Found("Location");
805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  }
81868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private:
835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  TrackInfo* track_info_;
845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)};
855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// Walk through a dictionary filling in |result| with track information. Return
875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// true if at least the id and location where found (artist and album may be
885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// empty).  In either case, the cursor is advanced out of the dictionary.
895f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)bool GetTrackInfoFromDict(XmlReader* reader, TrackInfo* result) {
905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  DCHECK(result);
915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  TrackInfoXmlDictReader dict_reader(reader, result);
925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  return dict_reader.Read();
93868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)}
94868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
95868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)}  // namespace
96868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
97868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)ITunesLibraryParser::ITunesLibraryParser() {}
98868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)ITunesLibraryParser::~ITunesLibraryParser() {}
99868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
100868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)bool ITunesLibraryParser::Parse(const std::string& library_xml) {
101868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  XmlReader reader;
102868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
103868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (!reader.Load(library_xml))
104868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return false;
105868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
106868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  // Find the plist node and then search within that tag.
1078bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  if (!iapps::SeekToNodeAtCurrentDepth(&reader, "plist"))
108868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return false;
109868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (!reader.Read())
110868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return false;
111868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
1128bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  if (!iapps::SeekToNodeAtCurrentDepth(&reader, "dict"))
113868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return false;
114868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
1158bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  if (!iapps::SeekInDict(&reader, "Tracks"))
116868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return false;
117868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
118868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  // Once inside the Tracks dict, we expect track dictionaries keyed by id. i.e.
119868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  //   <key>Tracks</key>
120868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  //   <dict>
121868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  //     <key>160</key>
122868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  //     <dict>
123868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  //       <key>Track ID</key><integer>160</integer>
1248bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  if (!iapps::SeekToNodeAtCurrentDepth(&reader, "dict"))
125868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return false;
126868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  int tracks_dict_depth = reader.Depth() + 1;
127868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (!reader.Read())
128868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return false;
129868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
130868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  // Once parsing has gotten this far, return whatever is found, even if
131868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  // some of the data isn't extracted just right.
132868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  bool no_errors = true;
133868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  bool track_found = false;
134868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  while (reader.Depth() >= tracks_dict_depth) {
1358bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)    if (!iapps::SeekToNodeAtCurrentDepth(&reader, "key"))
136868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      return track_found;
137868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    std::string key;  // Should match track id below.
138868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    if (!reader.ReadElementContent(&key))
139868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      return track_found;
140868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    uint64 id;
141868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    bool id_valid = base::StringToUint64(key, &id);
142868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    if (!reader.SkipToElement())
143868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      return track_found;
144868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
145868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    TrackInfo track_info;
146868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    if (GetTrackInfoFromDict(&reader, &track_info) &&
147868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)        id_valid &&
148868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)        id == track_info.id) {
149ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      if (track_info.artist.empty())
150ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        track_info.artist = "Unknown Artist";
151ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      if (track_info.album.empty())
152ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        track_info.album = "Unknown Album";
153eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      parser::Track track(track_info.id, track_info.location);
154868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      library_[track_info.artist][track_info.album].insert(track);
155868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      track_found = true;
156868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    } else {
157868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      no_errors = false;
158868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    }
159868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  }
160868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
161868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  return track_found || no_errors;
162868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)}
163868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
164868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)}  // namespace itunes
165