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