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 19 namespace itunes { 20 21 namespace { 22 23 struct TrackInfo { 24 uint64 id; 25 base::FilePath location; 26 std::string artist; 27 std::string album; 28 }; 29 30 class 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. 89 bool GetTrackInfoFromDict(XmlReader* reader, TrackInfo* result) { 90 DCHECK(result); 91 TrackInfoXmlDictReader dict_reader(reader, result); 92 return dict_reader.Read(); 93 } 94 95 } // namespace 96 97 ITunesLibraryParser::ITunesLibraryParser() {} 98 ITunesLibraryParser::~ITunesLibraryParser() {} 99 100 bool 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