blob: ba98d8e0c9600f7eb8d5a7d733a7871885961280 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/utility/media_galleries/iphoto_library_parser.h"
#include <string>
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "chrome/utility/media_galleries/iapps_xml_utils.h"
#include "third_party/libxml/chromium/libxml_utils.h"
namespace iphoto {
namespace {
struct PhotoInfo {
uint64 id;
base::FilePath location;
base::FilePath original_location;
};
struct AlbumInfo {
std::set<uint64> photo_ids;
std::string name;
uint64 id;
};
class PhotosXmlDictReader : public iapps::XmlDictReader {
public:
PhotosXmlDictReader(XmlReader* reader, PhotoInfo* photo_info)
: iapps::XmlDictReader(reader), photo_info_(photo_info) {}
bool HandleKeyImpl(const std::string& key) override {
if (key == "ImagePath") {
std::string value;
if (!iapps::ReadString(reader_, &value))
return false;
photo_info_->location = base::FilePath(value);
} else if (key == "OriginalPath") {
std::string value;
if (!iapps::ReadString(reader_, &value))
return false;
photo_info_->original_location = base::FilePath(value);
} else if (!SkipToNext()) {
return false;
}
return true;
}
bool FinishedOk() override { return Found("ImagePath"); }
private:
PhotoInfo* photo_info_;
};
// Contents of the album 'KeyList' key are
// <array>
// <string>1</string>
// <string>2</string>
// <string>3</string>
// </array>
bool ReadStringArray(XmlReader* reader, std::set<uint64>* photo_ids) {
if (reader->NodeName() != "array")
return false;
// Advance past the array node and into the body of the array.
if (!reader->Read())
return false;
int array_content_depth = reader->Depth();
while (iapps::SeekToNodeAtCurrentDepth(reader, "string")) {
if (reader->Depth() != array_content_depth)
return false;
std::string photo_id;
if (!iapps::ReadString(reader, &photo_id))
continue;
uint64 id;
if (!base::StringToUint64(photo_id, &id))
continue;
photo_ids->insert(id);
}
return true;
}
class AlbumXmlDictReader : public iapps::XmlDictReader {
public:
AlbumXmlDictReader(XmlReader* reader, AlbumInfo* album_info)
: iapps::XmlDictReader(reader), album_info_(album_info) {}
bool ShouldLoop() override {
return !(Found("AlbumId") && Found("AlbumName") && Found("KeyList"));
}
bool HandleKeyImpl(const std::string& key) override {
if (key == "AlbumId") {
if (!iapps::ReadInteger(reader_, &album_info_->id))
return false;
} else if (key == "AlbumName") {
if (!iapps::ReadString(reader_, &album_info_->name))
return false;
} else if (key == "KeyList") {
if (!iapps::SeekToNodeAtCurrentDepth(reader_, "array"))
return false;
if (!ReadStringArray(reader_, &album_info_->photo_ids))
return false;
} else if (!SkipToNext()) {
return false;
}
return true;
}
bool FinishedOk() override { return !ShouldLoop(); }
private:
AlbumInfo* album_info_;
};
// Inside the master image list, we expect photos to be arranged as
// <dict>
// <key>$PHOTO_ID</key>
// <dict>
// $photo properties
// </dict>
// <key>$PHOTO_ID</key>
// <dict>
// $photo properties
// </dict>
// ...
// </dict>
// Returns true on success, false on error.
bool ParseAllPhotos(XmlReader* reader,
std::set<iphoto::parser::Photo>* all_photos) {
if (!iapps::SeekToNodeAtCurrentDepth(reader, "dict"))
return false;
int photos_dict_depth = reader->Depth() + 1;
if (!reader->Read())
return false;
bool errors = false;
while (reader->Depth() >= photos_dict_depth) {
if (!iapps::SeekToNodeAtCurrentDepth(reader, "key"))
break;
std::string key;
if (!reader->ReadElementContent(&key)) {
errors = true;
break;
}
uint64 id;
bool id_valid = base::StringToUint64(key, &id);
if (!id_valid ||
reader->Depth() != photos_dict_depth) {
errors = true;
break;
}
if (!iapps::SeekToNodeAtCurrentDepth(reader, "dict")) {
errors = true;
break;
}
PhotoInfo photo_info;
photo_info.id = id;
// Walk through a dictionary filling in |result| with photo information.
// Return true if at least the location was found.
// In either case, the cursor is advanced out of the dictionary.
PhotosXmlDictReader dict_reader(reader, &photo_info);
if (!dict_reader.Read()) {
errors = true;
break;
}
parser::Photo photo(photo_info.id, photo_info.location,
photo_info.original_location);
all_photos->insert(photo);
}
return !errors;
}
} // namespace
IPhotoLibraryParser::IPhotoLibraryParser() {}
IPhotoLibraryParser::~IPhotoLibraryParser() {}
class IPhotoLibraryXmlDictReader : public iapps::XmlDictReader {
public:
IPhotoLibraryXmlDictReader(XmlReader* reader, parser::Library* library)
: iapps::XmlDictReader(reader), library_(library), ok_(true) {}
bool ShouldLoop() override {
return !(Found("List of Albums") && Found("Master Image List"));
}
bool HandleKeyImpl(const std::string& key) override {
if (key == "List of Albums") {
if (!iapps::SeekToNodeAtCurrentDepth(reader_, "array") ||
!reader_->Read()) {
return true;
}
while (iapps::SeekToNodeAtCurrentDepth(reader_, "dict")) {
AlbumInfo album_info;
AlbumXmlDictReader dict_reader(reader_, &album_info);
if (dict_reader.Read()) {
parser::Album album;
album = album_info.photo_ids;
// Strip / from album name and dedupe any collisions.
std::string name;
base::ReplaceChars(album_info.name, "//", " ", &name);
if (ContainsKey(library_->albums, name))
name = name + "("+base::Uint64ToString(album_info.id)+")";
library_->albums[name] = album;
}
}
} else if (key == "Master Image List") {
if (!ParseAllPhotos(reader_, &library_->all_photos)) {
ok_ = false;
return false;
}
}
return true;
}
bool FinishedOk() override { return ok_; }
// The IPhotoLibrary allows duplicate "List of Albums" and
// "Master Image List" keys (although that seems odd.)
bool AllowRepeats() override { return true; }
private:
parser::Library* library_;
// The base class bails when we request, and then calls |FinishedOk()|
// to decide what to return. We need to remember that we bailed because
// of an error. That's what |ok_| does.
bool ok_;
};
bool IPhotoLibraryParser::Parse(const std::string& library_xml) {
XmlReader reader;
if (!reader.Load(library_xml))
return false;
// Find the plist node and then search within that tag.
if (!iapps::SeekToNodeAtCurrentDepth(&reader, "plist"))
return false;
if (!reader.Read())
return false;
if (!iapps::SeekToNodeAtCurrentDepth(&reader, "dict"))
return false;
IPhotoLibraryXmlDictReader dict_reader(&reader, &library_);
return dict_reader.Read();
}
} // namespace iphoto