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 // Walk through a dictionary filling in |result| with photo information. Return
     33 // true if at least the id and location were found.
     34 // In either case, the cursor is advanced out of the dictionary.
     35 bool GetPhotoInfoFromDict(XmlReader* reader, PhotoInfo* result) {
     36   DCHECK(result);
     37   if (reader->NodeName() != "dict")
     38     return false;
     39 
     40   int dict_content_depth = reader->Depth() + 1;
     41   // Advance past the dict node and into the body of the dictionary.
     42   if (!reader->Read())
     43     return false;
     44 
     45   bool found_location = false;
     46   while (reader->Depth() >= dict_content_depth) {
     47     if (!iapps::SeekToNodeAtCurrentDepth(reader, "key"))
     48       break;
     49     std::string found_key;
     50     if (!reader->ReadElementContent(&found_key))
     51       break;
     52     DCHECK_EQ(dict_content_depth, reader->Depth());
     53 
     54     if (found_key == "ImagePath") {
     55       if (found_location)
     56         break;
     57       std::string value;
     58       if (!iapps::ReadString(reader, &value))
     59         break;
     60       result->location = base::FilePath(value);
     61       found_location = true;
     62     } else if (found_key == "OriginalPath") {
     63       std::string value;
     64       if (!iapps::ReadString(reader, &value))
     65         break;
     66       result->original_location = base::FilePath(value);
     67     } else {
     68       if (!iapps::SkipToNextElement(reader))
     69         break;
     70       if (!reader->Next())
     71         break;
     72     }
     73   }
     74 
     75   // Seek to the end of the dictionary
     76   while (reader->Depth() >= dict_content_depth)
     77     reader->Next();
     78 
     79   return found_location;
     80 }
     81 
     82 // Contents of the album 'KeyList' key are
     83 // <array>
     84 //  <string>1</string>
     85 //  <string>2</string>
     86 //  <string>3</string>
     87 // </array>
     88 bool ReadStringArray(XmlReader* reader, std::set<uint64>* photo_ids) {
     89   if (reader->NodeName() != "array")
     90     return false;
     91 
     92   // Advance past the array node and into the body of the array.
     93   if (!reader->Read())
     94     return false;
     95 
     96   int array_content_depth = reader->Depth();
     97 
     98   while (iapps::SeekToNodeAtCurrentDepth(reader, "string")) {
     99     if (reader->Depth() != array_content_depth)
    100       return false;
    101     std::string photo_id;
    102     if (!iapps::ReadString(reader, &photo_id))
    103       continue;
    104     uint64 id;
    105     if (!base::StringToUint64(photo_id, &id))
    106       continue;
    107     photo_ids->insert(id);
    108   }
    109 
    110   return true;
    111 }
    112 
    113 bool GetAlbumInfoFromDict(XmlReader* reader, AlbumInfo* result) {
    114   DCHECK(result);
    115   if (reader->NodeName() != "dict")
    116     return false;
    117 
    118   int dict_content_depth = reader->Depth() + 1;
    119   // Advance past the dict node and into the body of the dictionary.
    120   if (!reader->Read())
    121     return false;
    122 
    123   bool found_id = false;
    124   bool found_name = false;
    125   bool found_contents = false;
    126   while (reader->Depth() >= dict_content_depth &&
    127          !(found_id && found_name && found_contents)) {
    128     if (!iapps::SeekToNodeAtCurrentDepth(reader, "key"))
    129       break;
    130     std::string found_key;
    131     if (!reader->ReadElementContent(&found_key))
    132       break;
    133     DCHECK_EQ(dict_content_depth, reader->Depth());
    134 
    135     if (found_key == "AlbumId") {
    136       if (found_id)
    137         break;
    138       if (!iapps::ReadInteger(reader, &result->id))
    139         break;
    140       found_id = true;
    141     } else if (found_key == "AlbumName") {
    142       if (found_name)
    143         break;
    144       if (!iapps::ReadString(reader, &result->name))
    145         break;
    146       found_name = true;
    147     } else if (found_key == "KeyList") {
    148       if (found_contents)
    149         break;
    150       if (!iapps::SeekToNodeAtCurrentDepth(reader, "array"))
    151         break;
    152       if (!ReadStringArray(reader, &result->photo_ids))
    153         break;
    154       found_contents = true;
    155     } else {
    156       if (!iapps::SkipToNextElement(reader))
    157         break;
    158       if (!reader->Next())
    159         break;
    160     }
    161   }
    162 
    163   // Seek to the end of the dictionary
    164   while (reader->Depth() >= dict_content_depth)
    165     reader->Next();
    166 
    167   return found_id && found_name && found_contents;
    168 }
    169 
    170 // Inside the master image list, we expect photos to be arranged as
    171 //  <dict>
    172 //   <key>$PHOTO_ID</key>
    173 //   <dict>
    174 //     $photo properties
    175 //   </dict>
    176 //   <key>$PHOTO_ID</key>
    177 //   <dict>
    178 //     $photo properties
    179 //   </dict>
    180 //   ...
    181 //  </dict>
    182 // Returns true on success, false on error.
    183 bool ParseAllPhotos(XmlReader* reader,
    184                     std::set<iphoto::parser::Photo>* all_photos) {
    185   if (!iapps::SeekToNodeAtCurrentDepth(reader, "dict"))
    186     return false;
    187   int photos_dict_depth = reader->Depth() + 1;
    188   if (!reader->Read())
    189     return false;
    190 
    191   bool errors = false;
    192   while (reader->Depth() >= photos_dict_depth) {
    193     if (!iapps::SeekToNodeAtCurrentDepth(reader, "key"))
    194       break;
    195 
    196     std::string key;
    197     if (!reader->ReadElementContent(&key)) {
    198       errors = true;
    199       break;
    200     }
    201     uint64 id;
    202     bool id_valid = base::StringToUint64(key, &id);
    203 
    204     if (!id_valid ||
    205         reader->Depth() != photos_dict_depth) {
    206       errors = true;
    207       break;
    208     }
    209     if (!iapps::SeekToNodeAtCurrentDepth(reader, "dict")) {
    210       errors = true;
    211       break;
    212     }
    213 
    214     PhotoInfo photo_info;
    215     photo_info.id = id;
    216     if (!GetPhotoInfoFromDict(reader, &photo_info)) {
    217       errors = true;
    218       break;
    219     }
    220 
    221     parser::Photo photo(photo_info.id, photo_info.location,
    222                         photo_info.original_location);
    223     all_photos->insert(photo);
    224   }
    225 
    226   return !errors;
    227 }
    228 
    229 }  // namespace
    230 
    231 IPhotoLibraryParser::IPhotoLibraryParser() {}
    232 IPhotoLibraryParser::~IPhotoLibraryParser() {}
    233 
    234 bool IPhotoLibraryParser::Parse(const std::string& library_xml) {
    235   XmlReader reader;
    236   if (!reader.Load(library_xml))
    237     return false;
    238 
    239   // Find the plist node and then search within that tag.
    240   if (!iapps::SeekToNodeAtCurrentDepth(&reader, "plist"))
    241     return false;
    242   if (!reader.Read())
    243     return false;
    244 
    245   if (!iapps::SeekToNodeAtCurrentDepth(&reader, "dict"))
    246     return false;
    247 
    248   int dict_content_depth = reader.Depth() + 1;
    249   // Advance past the dict node and into the body of the dictionary.
    250   if (!reader.Read())
    251     return false;
    252 
    253   bool found_photos = false;
    254   bool found_albums = false;
    255   while (reader.Depth() >= dict_content_depth &&
    256          !(found_photos && found_albums)) {
    257     if (!iapps::SeekToNodeAtCurrentDepth(&reader, "key"))
    258       break;
    259     std::string found_key;
    260     if (!reader.ReadElementContent(&found_key))
    261       break;
    262     DCHECK_EQ(dict_content_depth, reader.Depth());
    263 
    264     if (found_key == "List of Albums") {
    265       if (found_albums)
    266         continue;
    267       found_albums = true;
    268 
    269       if (!iapps::SeekToNodeAtCurrentDepth(&reader, "array") ||
    270           !reader.Read()) {
    271         continue;
    272       }
    273 
    274       while (iapps::SeekToNodeAtCurrentDepth(&reader, "dict")) {
    275         AlbumInfo album_info;
    276         if (GetAlbumInfoFromDict(&reader, &album_info)) {
    277           parser::Album album;
    278           album = album_info.photo_ids;
    279           // Strip / from album name and dedupe any collisions.
    280           std::string name;
    281           base::ReplaceChars(album_info.name, "//", " ", &name);
    282           if (!ContainsKey(library_.albums, name)) {
    283             library_.albums[name] = album;
    284           } else {
    285             library_.albums[name+"("+base::Uint64ToString(album_info.id)+")"] =
    286                 album;
    287           }
    288         }
    289       }
    290     } else if (found_key == "Master Image List") {
    291       if (found_photos)
    292         continue;
    293       found_photos = true;
    294       if (!ParseAllPhotos(&reader, &library_.all_photos))
    295         return false;
    296     }
    297   }
    298 
    299   return true;
    300 }
    301 
    302 }  // namespace iphoto
    303