• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
TrackInfoXmlDictReader(XmlReader * reader,TrackInfo * track_info)32   TrackInfoXmlDictReader(XmlReader* reader, TrackInfo* track_info) :
33     iapps::XmlDictReader(reader), track_info_(track_info) {}
34 
ShouldLoop()35   virtual bool ShouldLoop() OVERRIDE {
36     return !(Found("Track ID") && Found("Location") &&
37              Found("Album Artist") && Found("Album"));
38   }
39 
HandleKeyImpl(const std::string & key)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 
FinishedOk()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.
GetTrackInfoFromDict(XmlReader * reader,TrackInfo * result)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 
ITunesLibraryParser()97 ITunesLibraryParser::ITunesLibraryParser() {}
~ITunesLibraryParser()98 ITunesLibraryParser::~ITunesLibraryParser() {}
99 
Parse(const std::string & library_xml)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