diff options
Diffstat (limited to 'Swiften/Avatars')
-rw-r--r-- | Swiften/Avatars/AvatarFileStorage.cpp | 26 | ||||
-rw-r--r-- | Swiften/Avatars/AvatarFileStorage.h | 24 | ||||
-rw-r--r-- | Swiften/Avatars/AvatarManager.cpp | 108 | ||||
-rw-r--r-- | Swiften/Avatars/AvatarManager.h | 51 | ||||
-rw-r--r-- | Swiften/Avatars/AvatarStorage.cpp | 8 | ||||
-rw-r--r-- | Swiften/Avatars/AvatarStorage.h | 18 | ||||
-rw-r--r-- | Swiften/Avatars/UnitTest/AvatarManagerTest.cpp | 110 | ||||
-rw-r--r-- | Swiften/Avatars/UnitTest/MockAvatarManager.cpp | 26 | ||||
-rw-r--r-- | Swiften/Avatars/UnitTest/MockAvatarManager.h | 17 |
9 files changed, 388 insertions, 0 deletions
diff --git a/Swiften/Avatars/AvatarFileStorage.cpp b/Swiften/Avatars/AvatarFileStorage.cpp new file mode 100644 index 0000000..1348018 --- /dev/null +++ b/Swiften/Avatars/AvatarFileStorage.cpp @@ -0,0 +1,26 @@ +#include "Swiften/Avatars/AvatarFileStorage.h" + +#include <iostream> +#include <boost/filesystem/fstream.hpp> + +namespace Swift { + +AvatarFileStorage::AvatarFileStorage(const boost::filesystem::path& path) : path_(path) { + boost::filesystem::create_directory(path_); +} + +bool AvatarFileStorage::hasAvatar(const String& hash) const { + return boost::filesystem::exists(getAvatarPath(hash)); +} + +void AvatarFileStorage::addAvatar(const String& hash, const ByteArray& avatar) { + boost::filesystem::ofstream file(getAvatarPath(hash), boost::filesystem::ofstream::binary|boost::filesystem::ofstream::out); + file.write(avatar.getData(), avatar.getSize()); + file.close(); +} + +boost::filesystem::path AvatarFileStorage::getAvatarPath(const String& hash) const { + return path_ / hash.getUTF8String(); +} + +} diff --git a/Swiften/Avatars/AvatarFileStorage.h b/Swiften/Avatars/AvatarFileStorage.h new file mode 100644 index 0000000..1afa703 --- /dev/null +++ b/Swiften/Avatars/AvatarFileStorage.h @@ -0,0 +1,24 @@ +#pragma once + +#include <map> +#include <boost/filesystem.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Avatars/AvatarStorage.h" + +namespace Swift { + class AvatarFileStorage : public AvatarStorage { + public: + AvatarFileStorage(const boost::filesystem::path& path); + + virtual bool hasAvatar(const String& hash) const; + virtual void addAvatar(const String& hash, const ByteArray& avatar); + + virtual boost::filesystem::path getAvatarPath(const String& hash) const; + + private: + boost::filesystem::path path_; + }; + +} diff --git a/Swiften/Avatars/AvatarManager.cpp b/Swiften/Avatars/AvatarManager.cpp new file mode 100644 index 0000000..3825ffd --- /dev/null +++ b/Swiften/Avatars/AvatarManager.cpp @@ -0,0 +1,108 @@ +#include "Swiften/Avatars/AvatarManager.h" + +#include <boost/bind.hpp> + +#include "Swiften/Client/StanzaChannel.h" +#include "Swiften/Elements/VCardUpdate.h" +#include "Swiften/Queries/Requests/GetVCardRequest.h" +#include "Swiften/StringCodecs/SHA1.h" +#include "Swiften/StringCodecs/Hexify.h" +#include "Swiften/Avatars/AvatarStorage.h" +#include "Swiften/MUC/MUCRegistry.h" + +namespace Swift { + +AvatarManager::AvatarManager(StanzaChannel* stanzaChannel, IQRouter* iqRouter, AvatarStorage* avatarStorage, MUCRegistry* mucRegistry) : stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), avatarStorage_(avatarStorage), mucRegistry_(mucRegistry) { + stanzaChannel->onPresenceReceived.connect(boost::bind(&AvatarManager::handlePresenceReceived, this, _1)); +} + +AvatarManager::AvatarManager() { + stanzaChannel_ = NULL; + iqRouter_ = NULL; + avatarStorage_ = NULL; + mucRegistry_ = NULL; +} + +AvatarManager::~AvatarManager() { + +} + +void AvatarManager::setMUCRegistry(MUCRegistry* mucRegistry) { + mucRegistry_ = mucRegistry; +} + +void AvatarManager::handlePresenceReceived(boost::shared_ptr<Presence> presence) { + boost::shared_ptr<VCardUpdate> update = presence->getPayload<VCardUpdate>(); + if (!update) { + return; + } + JID from = getAvatarJID(presence->getFrom()); + String& hash = avatarHashes_[from]; + if (hash != update->getPhotoHash()) { + String newHash = update->getPhotoHash(); + if (avatarStorage_->hasAvatar(newHash)) { + setAvatarHash(from, newHash); + } + else { + boost::shared_ptr<GetVCardRequest> request(new GetVCardRequest(from, iqRouter_)); + request->onResponse.connect(boost::bind(&AvatarManager::handleVCardReceived, this, from, newHash, _1, _2)); + request->send(); + } + } +} + +void AvatarManager::handleVCardReceived(const JID& from, const String& promisedHash, boost::shared_ptr<VCard> vCard, const boost::optional<ErrorPayload>& error) { + if (error) { + // FIXME: What to do here? + std::cerr << "Warning: " << from << ": Could not get vCard" << std::endl; + return; + } + if (!vCard) { + std::cerr << "Warning: " << from << ": null vcard payload" << std::endl; + //FIXME: Why could this happen? + return; + } + String realHash = Hexify::hexify(SHA1::getHash(vCard->getPhoto())); + if (promisedHash != realHash) { + std::cerr << "Warning: " << from << ": Got different vCard photo hash (" << promisedHash << " != " << realHash << ")" << std::endl; + } + avatarStorage_->addAvatar(realHash, vCard->getPhoto()); + setAvatarHash(from, realHash); +} + +void AvatarManager::setAvatar(const JID& jid, const ByteArray& avatar) { + String hash = Hexify::hexify(SHA1::getHash(avatar)); + avatarStorage_->addAvatar(hash, avatar); + setAvatarHash(getAvatarJID(jid), hash); +} + +void AvatarManager::setAvatarHash(const JID& from, const String& hash) { + avatarHashes_[from] = hash; + onAvatarChanged(from, hash); +} + +String AvatarManager::getAvatarHash(const JID& jid) const { + std::map<JID, String>::const_iterator i = avatarHashes_.find(getAvatarJID(jid)); + if (i != avatarHashes_.end()) { + return i->second; + } + else { + return ""; + } +} + +boost::filesystem::path AvatarManager::getAvatarPath(const JID& jid) const { + String hash = getAvatarHash(jid); + if (!hash.isEmpty()) { + return avatarStorage_->getAvatarPath(hash); + } + return boost::filesystem::path(); +} + +JID AvatarManager::getAvatarJID(const JID& jid) const { + JID bareFrom = jid.toBare(); + return (mucRegistry_ && mucRegistry_->isMUC(bareFrom)) ? jid : bareFrom; +} + + +} diff --git a/Swiften/Avatars/AvatarManager.h b/Swiften/Avatars/AvatarManager.h new file mode 100644 index 0000000..fd308d9 --- /dev/null +++ b/Swiften/Avatars/AvatarManager.h @@ -0,0 +1,51 @@ +#pragma once + +#include <boost/filesystem.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> +#include <boost/signal.hpp> +#include <map> + +#include "Swiften/JID/JID.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Elements/VCard.h" +#include "Swiften/Elements/ErrorPayload.h" + +namespace Swift { + class MUCRegistry; + class AvatarStorage; + class StanzaChannel; + class IQRouter; + + class AvatarManager { + public: + AvatarManager(StanzaChannel*, IQRouter*, AvatarStorage*, MUCRegistry* = NULL); + virtual ~AvatarManager(); + + virtual void setMUCRegistry(MUCRegistry*); + + virtual String getAvatarHash(const JID&) const; + virtual boost::filesystem::path getAvatarPath(const JID&) const; + virtual void setAvatar(const JID&, const ByteArray& avatar); + + public: + boost::signal<void (const JID&, const String&)> onAvatarChanged; + + protected: + /** Used only for testing. Leads to a non-functional object. */ + AvatarManager(); + + private: + void handlePresenceReceived(boost::shared_ptr<Presence>); + void handleVCardReceived(const JID& from, const String& hash, boost::shared_ptr<VCard>, const boost::optional<ErrorPayload>&); + void setAvatarHash(const JID& from, const String& hash); + JID getAvatarJID(const JID& o) const; + + private: + StanzaChannel* stanzaChannel_; + IQRouter* iqRouter_; + AvatarStorage* avatarStorage_; + MUCRegistry* mucRegistry_; + std::map<JID, String> avatarHashes_; + }; +} diff --git a/Swiften/Avatars/AvatarStorage.cpp b/Swiften/Avatars/AvatarStorage.cpp new file mode 100644 index 0000000..4c98314 --- /dev/null +++ b/Swiften/Avatars/AvatarStorage.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Avatars/AvatarStorage.h" + +namespace Swift { + +AvatarStorage::~AvatarStorage() { +} + +} diff --git a/Swiften/Avatars/AvatarStorage.h b/Swiften/Avatars/AvatarStorage.h new file mode 100644 index 0000000..b5c0f32 --- /dev/null +++ b/Swiften/Avatars/AvatarStorage.h @@ -0,0 +1,18 @@ +#pragma once + +#include <boost/filesystem.hpp> + +namespace Swift { + class String; + class ByteArray; + + class AvatarStorage { + public: + virtual ~AvatarStorage(); + + virtual bool hasAvatar(const String& hash) const = 0; + virtual void addAvatar(const String& hash, const ByteArray& avatar) = 0; + virtual boost::filesystem::path getAvatarPath(const String& hash) const = 0; + }; + +} diff --git a/Swiften/Avatars/UnitTest/AvatarManagerTest.cpp b/Swiften/Avatars/UnitTest/AvatarManagerTest.cpp new file mode 100644 index 0000000..3b4c5f9 --- /dev/null +++ b/Swiften/Avatars/UnitTest/AvatarManagerTest.cpp @@ -0,0 +1,110 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Elements/VCardUpdate.h" +#include "Swiften/Avatars/AvatarManager.h" +#include "Swiften/Avatars/AvatarStorage.h" +#include "Swiften/MUC/MUCRegistry.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Client/DummyStanzaChannel.h" + +using namespace Swift; + +class AvatarManagerTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(AvatarManagerTest); + CPPUNIT_TEST(testUpdate_UpdateNewHash); + CPPUNIT_TEST(testUpdate_UpdateNewHashAlreadyHaveAvatar); + CPPUNIT_TEST(testUpdate_UpdateNewHashFromMUC); + CPPUNIT_TEST(testUpdate_UpdateSameHash); + CPPUNIT_TEST(testUpdate_UpdateNewHashSameThanOtherUser); + CPPUNIT_TEST(testReceiveVCard); + CPPUNIT_TEST(testGetAvatarPath); + CPPUNIT_TEST(testGetAvatarPathFromMUC); + CPPUNIT_TEST_SUITE_END(); + + public: + AvatarManagerTest() {} + + void setUp() { + stanzaChannel_ = new DummyStanzaChannel(); + iqRouter_ = new IQRouter(stanzaChannel_); + mucRegistry_ = new DummyMUCRegistry(); + avatarStorage_ = new DummyAvatarStorage(); + } + + void tearDown() { + delete avatarStorage_; + delete mucRegistry_; + delete iqRouter_; + delete stanzaChannel_; + } + + void testUpdate_UpdateNewHash() { + std::auto_ptr<AvatarManager> testling = createManager(); + stanzaChannel_->onPresenceReceived(createPresenceWithPhotoHash()); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel_->sentStanzas_.size())); + IQ* + CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<VCardUpdate>(0, JID("foo@bar.com"), IQ::Get)); + } + + void testUpdate_UpdateNewHashAlreadyHaveAvatar() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + void testUpdate_UpdateNewHashFromMUC() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + void testUpdate_UpdateSameHash() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + void testUpdate_UpdateNewHashSameThanOtherUser() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + void testReceiveVCard() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + void testGetAvatarPath() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + void testGetAvatarPathFromMUC() { + std::auto_ptr<AvatarManager> testling = createManager(); + } + + private: + std::auto_ptr<AvatarManager> createManager() { + return std::auto_ptr<AvatarManager>(new AvatarManager(stanzaChannel_, iqRouter_, avatarStorage_, mucRegistry_)); + } + + boost::shared_ptr<Presence> createPresenceWithPhotoHash() { + boost::shared_ptr<Presence> presence(new Presence()); + presence->setFrom(JID("foo@bar.com/baz")); + presence->addPayload(boost::shared_ptr<VCardUpdate>(new VCardUpdate("aef56135bcce35eb24a43fcd684005b4ca286497"))); + return presence; + } + + private: + struct DummyMUCRegistry : public MUCRegistry { + bool isMUC(const JID& jid) const { return std::find(mucs_.begin(), mucs_.end(), jid) != mucs_.end(); } + std::vector<JID> mucs_; + }; + struct DummyAvatarStorage : public AvatarStorage { + virtual bool hasAvatar(const String& hash) const { return avatars.find(hash) != avatars.end(); } + virtual void addAvatar(const String& hash, const ByteArray& avatar) { avatars[hash] = avatar; } + virtual boost::filesystem::path getAvatarPath(const String& hash) const { + return boost::filesystem::path("/avatars") / hash.getUTF8String(); + } + std::map<String, ByteArray> avatars; + }; + DummyStanzaChannel* stanzaChannel_; + IQRouter* iqRouter_; + DummyMUCRegistry* mucRegistry_; + DummyAvatarStorage* avatarStorage_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(AvatarManagerTest); diff --git a/Swiften/Avatars/UnitTest/MockAvatarManager.cpp b/Swiften/Avatars/UnitTest/MockAvatarManager.cpp new file mode 100644 index 0000000..2d3d48a --- /dev/null +++ b/Swiften/Avatars/UnitTest/MockAvatarManager.cpp @@ -0,0 +1,26 @@ +#include "Swiften/Avatars/UnitTest/MockAvatarManager.h" + +namespace Swift { + +MockAvatarManager::MockAvatarManager() { + +} + +MockAvatarManager::~MockAvatarManager() { + +} + +String MockAvatarManager::getAvatarHash(const JID& jid) const { + return jid.toBare(); +} + +boost::filesystem::path MockAvatarManager::getAvatarPath(const JID& jid) const { + return jid.getResource().getUTF8String(); +} + +void MockAvatarManager::setAvatar(const JID&, const ByteArray&) { + +} + +} + diff --git a/Swiften/Avatars/UnitTest/MockAvatarManager.h b/Swiften/Avatars/UnitTest/MockAvatarManager.h new file mode 100644 index 0000000..416d921 --- /dev/null +++ b/Swiften/Avatars/UnitTest/MockAvatarManager.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Swiften/Avatars/AvatarManager.h" +#include "Swiften/Client/DummyStanzaChannel.h" + +namespace Swift { + class MockAvatarManager : public AvatarManager { + public: + MockAvatarManager(); + virtual ~MockAvatarManager(); + virtual String getAvatarHash(const JID&) const; + virtual boost::filesystem::path getAvatarPath(const JID&) const; + virtual void setAvatar(const JID&, const ByteArray& avatar); + private: + DummyStanzaChannel channel_; + }; +} |