diff --git a/app/models/jobs/http_post.rb b/app/models/jobs/http_post.rb
index 5a63a4198..16764a579 100644
--- a/app/models/jobs/http_post.rb
+++ b/app/models/jobs/http_post.rb
@@ -2,9 +2,11 @@ module Jobs
class HttpPost
extend ResqueJobLogging
@queue = :http
+ NUM_TRIES = 3
- def self.perform(url, body, tries_remaining)
+ def self.perform(url, body, tries_remaining = NUM_TRIES)
begin
+ body = CGI::escape(body)
RestClient.post(url, :xml => body){ |response, request, result, &block|
if [301, 302, 307].include? response.code
response.follow_redirection(request, result, &block)
diff --git a/app/views/publics/hcard.html.haml b/app/views/publics/hcard.html.haml
new file mode 100644
index 000000000..f6e4c4408
--- /dev/null
+++ b/app/views/publics/hcard.html.haml
@@ -0,0 +1,50 @@
+#content
+ %h1= @person.name
+ #content_inner
+ #i.entity_profile.vcard.author
+ %h2 User profile
+
+ %dl.entity_nickname
+ %dt Nickname
+ %dd
+ %a.nickname.url.uid{:href=>@person.url, :rel=>'me'}= @person.name
+
+ %dl.entity_given_name
+ %dt First name
+ %dd
+ %span.given_name= @person.profile.first_name
+
+ %dl.entity_family_name
+ %dt Family name
+ %dd
+ %span.family_name= @person.profile.last_name
+
+ %dl.entity_fn
+ %dt Full name
+ %dd
+ %span.fn= @person.name
+
+ %dl.entity_url
+ %dt URL
+ %dd
+ %a#pod_location.url{:href=>@person.url, :rel=>'me'}= @person.url
+
+ %dl.entity_photo
+ %dt Photo
+ %dd
+ %img.photo.avatar{:src=>image_or_default(@person), :width=>'300px', :height=>'300px'}
+
+ %dl.entity_photo_medium
+ %dt Photo
+ %dd
+ %img.photo.avatar{:src=>image_or_default(@person, :thumb_medium), :width=>'100px', :height=>'100px'}
+
+ %dl.entity_photo_small
+ %dt Photo
+ %dd
+ %img.photo.avatar{:src=>image_or_default(@person, :thumb_small), :width=>'50px', :height=>'50px'}
+
+ %dl.entity_searchable
+ %dt Searchable
+ %dd
+ %span.searchable= @person.profile.searchable
diff --git a/app/views/publics/host_meta.html.erb b/app/views/publics/host_meta.html.erb
new file mode 100644
index 000000000..8b0ad3aa8
--- /dev/null
+++ b/app/views/publics/host_meta.html.erb
@@ -0,0 +1,9 @@
+
+
+ <%= AppConfig[:pod_uri].host %>
+
+ Resource Descriptor
+
+
diff --git a/app/views/publics/webfinger.html.erb b/app/views/publics/webfinger.html.erb
new file mode 100644
index 000000000..8c6d08657
--- /dev/null
+++ b/app/views/publics/webfinger.html.erb
@@ -0,0 +1,12 @@
+
+
+ acct:<%=@person.diaspora_handle%>
+ "<%= @person.url %>"
+
+
+
+
+
+
+
+
diff --git a/config/application.rb b/config/application.rb
index 87498fa4d..4b1b83786 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -13,7 +13,6 @@ Bundler.require(:default, Rails.env) if defined?(Bundler)
require File.expand_path('../../lib/mongo_mapper/bson_id', __FILE__)
require File.expand_path('../../lib/log_overrider', __FILE__)
-require File.expand_path('../../lib/message_handler', __FILE__)
module Diaspora
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
diff --git a/lib/postzord.rb b/lib/postzord.rb
new file mode 100644
index 000000000..0c652d738
--- /dev/null
+++ b/lib/postzord.rb
@@ -0,0 +1,7 @@
+# Copyright (c) 2010, Diaspora Inc. This file is
+# licensed under the Affero General Public License version 3 or later. See
+# the COPYRIGHT file.
+
+class Postzord
+
+end
diff --git a/lib/postzord/dispatch.rb b/lib/postzord/dispatch.rb
new file mode 100644
index 000000000..f74c365da
--- /dev/null
+++ b/lib/postzord/dispatch.rb
@@ -0,0 +1,67 @@
+# Copyright (c) 2010, Diaspora Inc. This file is
+# licensed under the Affero General Public License version 3 or later. See
+# the COPYRIGHT file.
+
+class Postzord::Dispatch
+ def initialize(user, object)
+ unless object.respond_to? :to_diaspora_xml
+ raise 'this object does not respond_to? to_diaspora xml. try including Diaspora::Webhooks into your object'
+ end
+ @sender = user
+ @sender_person = @sender.person
+ @object = object
+ @xml = @object.to_diaspora_xml
+ @subscribers = @object.subscribers
+ @salmon_factory = Salmon::SalmonSlap.create(@sender, @xml)
+ end
+
+ def post(opts = {})
+ remote_people, local_people = @subscribers.partition{ |person| person.owner_id.nil? }
+ user_ids = [*local_people].map{|x| x.owner_id }
+ local_users = User.all(:id.in => user_ids)
+ self.socket_to_users(local_users)
+ self.deliver_to_remote(remote_people)
+ self.deliver_to_local(local_people)
+ self.deliver_to_services(opts[:url])
+ end
+
+ protected
+ def deliver_to_remote(people)
+ people.each do |person|
+ enc_xml = @salmon_factory.xml_for(person)
+ Rails.logger.info("event=push_to_person route=remote sender=#{@sender.person.diaspora_handle} recipient=#{person.diaspora_handle} payload_type=#{@object.class}")
+ Resque.enqueue(Jobs::HttpPost, person.receive_url, enc_xml)
+ end
+ end
+
+ def deliver_to_local(people)
+ people.each do |person|
+ Rails.logger.info("event=push_to_local_person route=local sender=#{@sender_person.diaspora_handle} recipient=#{person.diaspora_handle} payload_type=#{@object.class}")
+ Resque.enqueue(Jobs::Receive, person.owner_id, @xml, @sender_person.id)
+ end
+ end
+
+ def deliver_to_hub
+ Rails.logger.debug("event=post_to_service type=pubsub sender_handle=#{@sender.diaspora_handle}")
+ EventMachine::PubSubHubbub.new(AppConfig[:pubsub_server]).publish(@sender.public_url)
+ end
+
+ def deliver_to_services(url)
+ if @object.respond_to?(:public) && @object.public
+ deliver_to_hub
+ if @object.respond_to?(:message)
+ @sender.services.each do |service|
+ service.post(@object, url)
+ end
+ end
+ end
+ end
+
+ def socket_to_users(users)
+ if @object.respond_to?(:socket_to_uid)
+ users.each do |user|
+ @object.socket_to_uid(user)
+ end
+ end
+ end
+end
diff --git a/spec/lib/postzord/dispatch_spec.rb b/spec/lib/postzord/dispatch_spec.rb
new file mode 100644
index 000000000..011940f71
--- /dev/null
+++ b/spec/lib/postzord/dispatch_spec.rb
@@ -0,0 +1,156 @@
+# Copyright (c) 2010, Diaspora Inc. This file is
+# licensed under the Affero General Public License version 3 or later. See
+# the COPYRIGHT file.
+
+require 'spec_helper'
+
+require File.join(Rails.root, 'lib/postzord')
+require File.join(Rails.root, 'lib/postzord/dispatch')
+
+describe Postzord::Dispatch do
+ before do
+ @user = make_user
+ @sm = Factory(:status_message, :public => true)
+ @subscribers = []
+ 5.times{@subscribers << Factory(:person)}
+ @sm.stub!(:subscribers)
+ @xml = @sm.to_diaspora_xml
+ end
+
+ describe '.initialize' do
+ it 'takes an sender(User) and object (responds_to #subscibers) and sets then to @sender and @object' do
+ zord = Postzord::Dispatch.new(@user, @sm)
+ zord.instance_variable_get(:@sender).should == @user
+ zord.instance_variable_get(:@object).should == @sm
+ end
+
+ it 'sets @subscribers from object' do
+ @sm.should_receive(:subscribers).and_return(@subscribers)
+ zord = Postzord::Dispatch.new(@user, @sm)
+ zord.instance_variable_get(:@subscribers).should == @subscribers
+ end
+
+ it 'sets the @sender_person object' do
+
+ zord = Postzord::Dispatch.new(@user, @sm)
+ zord.instance_variable_get(:@sender_person).should == @user.person
+ end
+
+ it 'raises and gives you a helpful message if the object can not federate' do
+ proc{ Postzord::Dispatch.new(@user, [])
+ }.should raise_error /Diaspora::Webhooks/
+ end
+
+ it 'creates a salmon base object' do
+ zord = Postzord::Dispatch.new(@user, @sm)
+ zord.instance_variable_get(:@salmon_factory).should_not be nil
+ end
+ end
+
+ context 'instance methods' do
+ before do
+ @local_user = make_user
+ @subscribers << @local_user.person
+ @remote_people, @local_people = @subscribers.partition{ |person| person.owner_id.nil? }
+ @sm.stub!(:subscribers).and_return @subscribers
+ @zord = Postzord::Dispatch.new(@user, @sm)
+ end
+
+ describe '#post' do
+ before do
+ @zord.stub!(:socket_to_users)
+ end
+ it 'calls Array#partition on subscribers' do
+ @subscribers.should_receive(:partition).and_return([@remote_people, @local_people])
+ @zord.post
+ end
+
+ it 'calls #deliver_to_local with local people' do
+ @zord.should_receive(:deliver_to_local).with(@local_people)
+ @zord.post
+ end
+
+ it 'calls #deliver_to_remote with remote people' do
+ @zord.should_receive(:deliver_to_remote).with(@remote_people)
+ @zord.post
+ end
+
+ it 'calls socket_to_users with the local users' do
+ @zord.should_receive(:socket_to_users).with([@local_user])
+ @zord.post
+ end
+ end
+
+ describe '#deliver_to_remote' do
+
+ before do
+ @remote_people = []
+ @remote_people << @user.person
+ @mailman = Postzord::Dispatch.new(@user, @sm)
+ end
+
+ it 'should queue an HttpPost job for each remote person' do
+ Resque.should_receive(:enqueue).with(Jobs::HttpPost, @user.person.receive_url, anything).once
+ @mailman.send(:deliver_to_remote, @remote_people)
+ end
+
+ it 'calls salmon_for each remote person' do
+ salmon = @mailman.instance_variable_get(:@salmon_factory)
+ salmon.should_receive(:xml_for).with(@user.person)
+ @mailman.send(:deliver_to_remote, @remote_people)
+ end
+ end
+
+ describe '#deliver_to_local' do
+ it 'sends each person an object' do
+ local_people = []
+ local_people << @user.person
+ mailman = Postzord::Dispatch.new(@user, @sm)
+ Resque.should_receive(:enqueue).with(Jobs::Receive, @user.id, @xml, anything).once
+ mailman.send(:deliver_to_local, local_people)
+ end
+ end
+
+ describe '#deliver_to_services' do
+ before do
+ @user.aspects.create(:name => "whatever")
+ @service = Services::Facebook.new(:access_token => "yeah")
+ @user.services << @service
+ end
+
+ it 'calls post for each of the users services' do
+ @service.should_receive(:post).once
+ @zord.instance_variable_get(:@sender).should_receive(:services).and_return([@service])
+ @zord.send(:deliver_to_services, nil)
+ end
+
+ it 'notifies the hub' do
+ @zord.should_receive(:deliver_to_hub)
+ @zord.send(:deliver_to_services, nil)
+ end
+
+ it 'only pushes to services if the object is public' do
+ mailman = Postzord::Dispatch.new(@user, Factory(:status_message))
+
+ mailman.should_not_receive(:deliver_to_hub)
+ mailman.instance_variable_get(:@user).should_not_receive(:services)
+ end
+ end
+
+ describe '#socket_to_users' do
+ it 'should call object#socket_to_uid for each local user' do
+ @zord.instance_variable_get(:@object).should_receive(:socket_to_uid)
+ @zord.send(:socket_to_users, [@local_user])
+ end
+
+ it 'only tries to socket when the object responds to #socket_to_uid' do
+ f = Request.new
+ f.stub!(:subscribers)
+ users = [@user]
+ z = Postzord::Dispatch.new(@user, f)
+ users.should_not_receive(:each) # checking if each is called due to respond_to, actually trying to
+ z.send(:socket_to_users, users)
+ end
+ end
+ end
+end
diff --git a/spec/models/jobs/http_post_spec.rb b/spec/models/jobs/http_post_spec.rb
index 18461996b..f5301d6cf 100644
--- a/spec/models/jobs/http_post_spec.rb
+++ b/spec/models/jobs/http_post_spec.rb
@@ -4,13 +4,14 @@ describe Jobs::HttpPost do
before do
@url = 'example.org/things/on/fire'
@body = 'California'
+ @escaped_body = CGI::escape(@body)
end
it 'POSTs to a given URL' do
- RestClient.should_receive(:post).with(@url, {:xml=>@body}).and_return(true)
+ RestClient.should_receive(:post).with(@url, {:xml=>@escaped_body}).and_return(true)
Jobs::HttpPost.perform(@url, @body, 3)
end
it 'retries' do
- RestClient.should_receive(:post).with(@url, {:xml=>@body}).and_raise(SocketError)
+ RestClient.should_receive(:post).with(@url, {:xml=>@escaped_body}).and_raise(SocketError)
Resque.should_receive(:enqueue).with(Jobs::HttpPost, @url, @body, 1).once
Jobs::HttpPost.perform(@url, @body, 2)
end