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/iphoto_library_parser.h"
      6 
      7 #include <string>
      8 
      9 #include "base/logging.h"
     10 #include "base/stl_util.h"
     11 #include "base/strings/string_number_conversions.h"
     12 #include "base/strings/string_util.h"
     13 #include "chrome/utility/media_galleries/iapps_xml_utils.h"
     14 #include "third_party/libxml/chromium/libxml_utils.h"
     15 
     16 namespace iphoto {
     17 
     18 namespace {
     19 
     20 struct PhotoInfo {
     21   uint64 id;
     22   base::FilePath location;
     23   base::FilePath original_location;
     24 };
     25 
     26 struct AlbumInfo {
     27   std::set<uint64> photo_ids;
     28   std::string name;
     29   uint64 id;
     30 };
     31 
     32 class PhotosXmlDictReader : public iapps::XmlDictReader {
     33  public:
     34   PhotosXmlDictReader(XmlReader* reader, PhotoInfo* photo_info)
     35     : iapps::XmlDictReader(reader), photo_info_(photo_info) {}
     36 
     37   virtual bool HandleKeyImpl(const std::string& key) OVERRIDE {
     38     if (key == "ImagePath") {
     39       std::string value;
     40       if (!iapps::ReadString(reader_, &value))
     41         return false;
     42       photo_info_->location = base::FilePath(value);
     43     } else if (key == "OriginalPath") {
     44       std::string value;
     45       if (!iapps::ReadString(reader_, &value))
     46         return false;
     47       photo_info_->original_location = base::FilePath(value);
     48     } else if (!SkipToNext()) {
     49       return false;
     50     }
     51     return true;
     52   }
     53 
     54   virtual bool FinishedOk() OVERRIDE {
     55     return Found("ImagePath");
     56   }
     57 
     58  private:
     59   PhotoInfo* photo_info_;
     60 };
     61 
     62 // Contents of the album 'KeyList' key are
     63 // <array>
     64 //  <string>1</string>
     65 //  <string>2</string>
     66 //  <string>3</string>
     67 // </array>
     68 bool ReadStringArray(XmlReader* reader, std::set<uint64>* photo_ids) {
     69   if (reader->NodeName() != "array")
     70     return false;
     71 
     72   // Advance past the array node and into the body of the array.
     73   if (!reader->Read())
     74     return false;
     75 
     76   int array_content_depth = reader->Depth();
     77 
     78   while (iapps::SeekToNodeAtCurrentDepth(reader, "string")) {
     79     if (reader->Depth() != array_content_depth)
     80       return false;
     81     std::string photo_id;
     82     if (!iapps::ReadString(reader, &photo_id))
     83       continue;
     84     uint64 id;
     85     if (!base::StringToUint64(photo_id, &id))
     86       continue;
     87     photo_ids->insert(id);
     88   }
     89 
     90   return true;
     91 }
     92 
     93 class AlbumXmlDictReader : public iapps::XmlDictReader {
     94  public:
     95   AlbumXmlDictReader(XmlReader* reader, AlbumInfo* album_info)
     96     : iapps::XmlDictReader(reader), album_info_(album_info) {}
     97 
     98   virtual bool ShouldLoop() OVERRIDE {
     99     return !(Found("AlbumId") && Found("AlbumName") && Found("KeyList"));
    100   }
    101 
    102   virtual bool HandleKeyImpl(const std::string& key) OVERRIDE {
    103     if (key == "AlbumId") {
    104       if (!iapps::ReadInteger(reader_, &album_info_->id))
    105         return false;
    106     } else if (key == "AlbumName") {
    107       if (!iapps::ReadString(reader_, &album_info_->name))
    108         return false;
    109     } else if (key == "KeyList") {
    110       if (!iapps::SeekToNodeAtCurrentDepth(reader_, "array"))
    111         return false;
    112       if (!ReadStringArray(reader_, &album_info_->photo_ids))
    113         return false;
    114     } else if (!SkipToNext()) {
    115       return false;
    116     }
    117     return true;
    118   }
    119 
    120   virtual bool FinishedOk() OVERRIDE {
    121     return !ShouldLoop();
    122   }
    123 
    124  private:
    125   AlbumInfo* album_info_;
    126 };
    127 
    128 // Inside the master image list, we expect photos to be arranged as
    129 //  <dict>
    130 //   <key>$PHOTO_ID</key>
    131 //   <dict>
    132 //     $photo properties
    133 //   </dict>
    134 //   <key>$PHOTO_ID</key>
    135 //   <dict>
    136 //     $photo properties
    137 //   </dict>
    138 //   ...
    139 //  </dict>
    140 // Returns true on success, false on error.
    141 bool ParseAllPhotos(XmlReader* reader,
    142                     std::set<iphoto::parser::Photo>* all_photos) {
    143   if (!iapps::SeekToNodeAtCurrentDepth(reader, "dict"))
    144     return false;
    145   int photos_dict_depth = reader->Depth() + 1;
    146   if (!reader->Read())
    147     return false;
    148 
    149   bool errors = false;
    150   while (reader->Depth() >= photos_dict_depth) {
    151     if (!iapps::SeekToNodeAtCurrentDepth(reader, "key"))
    152       break;
    153 
    154     std::string key;
    155     if (!reader->ReadElementContent(&key)) {
    156       errors = true;
    157       break;
    158     }
    159     uint64 id;
    160     bool id_valid = base::StringToUint64(key, &id);
    161 
    162     if (!id_valid ||
    163         reader->Depth() != photos_dict_depth) {
    164       errors = true;
    165       break;
    166     }
    167     if (!iapps::SeekToNodeAtCurrentDepth(reader, "dict")) {
    168       errors = true;
    169       break;
    170     }
    171 
    172     PhotoInfo photo_info;
    173     photo_info.id = id;
    174     // Walk through a dictionary filling in |result| with photo information.
    175     // Return true if at least the location was found.
    176     // In either case, the cursor is advanced out of the dictionary.
    177     PhotosXmlDictReader dict_reader(reader, &photo_info);
    178     if (!dict_reader.Read()) {
    179       errors = true;
    180       break;
    181     }
    182 
    183     parser::Photo photo(photo_info.id, photo_info.location,
    184                         photo_info.original_location);
    185     all_photos->insert(photo);
    186   }
    187 
    188   return !errors;
    189 }
    190 
    191 }  // namespace
    192 
    193 IPhotoLibraryParser::IPhotoLibraryParser() {}
    194 IPhotoLibraryParser::~IPhotoLibraryParser() {}
    195 
    196 class IPhotoLibraryXmlDictReader : public iapps::XmlDictReader {
    197  public:
    198   IPhotoLibraryXmlDictReader(XmlReader* reader, parser::Library* library)
    199     : iapps::XmlDictReader(reader), library_(library), ok_(true) {}
    200 
    201   virtual bool ShouldLoop() OVERRIDE {
    202     return !(Found("List of Albums") && Found("Master Image List"));
    203   }
    204 
    205   virtual bool HandleKeyImpl(const std::string& key) OVERRIDE {
    206     if (key == "List of Albums") {
    207       if (!iapps::SeekToNodeAtCurrentDepth(reader_, "array") ||
    208           !reader_->Read()) {
    209         return true;
    210       }
    211       while (iapps::SeekToNodeAtCurrentDepth(reader_, "dict")) {
    212         AlbumInfo album_info;
    213         AlbumXmlDictReader dict_reader(reader_, &album_info);
    214         if (dict_reader.Read()) {
    215           parser::Album album;
    216           album = album_info.photo_ids;
    217           // Strip / from album name and dedupe any collisions.
    218           std::string name;
    219           base::ReplaceChars(album_info.name, "//", " ", &name);
    220           if (ContainsKey(library_->albums, name))
    221             name = name + "("+base::Uint64ToString(album_info.id)+")";
    222           library_->albums[name] = album;
    223         }
    224       }
    225     } else if (key == "Master Image List") {
    226       if (!ParseAllPhotos(reader_, &library_->all_photos)) {
    227         ok_ = false;
    228         return false;
    229       }
    230     }
    231     return true;
    232   }
    233 
    234   virtual bool FinishedOk() OVERRIDE {
    235     return ok_;
    236   }
    237 
    238   // The IPhotoLibrary allows duplicate "List of Albums" and
    239   // "Master Image List" keys (although that seems odd.)
    240   virtual bool AllowRepeats() OVERRIDE {
    241     return true;
    242   }
    243 
    244  private:
    245   parser::Library* library_;
    246 
    247   // The base class bails when we request, and then calls |FinishedOk()|
    248   // to decide what to return. We need to remember that we bailed because
    249   // of an error. That's what |ok_| does.
    250   bool ok_;
    251 };
    252 
    253 bool IPhotoLibraryParser::Parse(const std::string& library_xml) {
    254   XmlReader reader;
    255   if (!reader.Load(library_xml))
    256     return false;
    257 
    258   // Find the plist node and then search within that tag.
    259   if (!iapps::SeekToNodeAtCurrentDepth(&reader, "plist"))
    260     return false;
    261   if (!reader.Read())
    262     return false;
    263 
    264   if (!iapps::SeekToNodeAtCurrentDepth(&reader, "dict"))
    265     return false;
    266 
    267   IPhotoLibraryXmlDictReader dict_reader(&reader, &library_);
    268   return dict_reader.Read();
    269 }
    270 
    271 }  // namespace iphoto
    272