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