Home | History | Annotate | Download | only in media_galleries
      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