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