handle existing guids on receive
This commit is contained in:
parent
f3466bcfd6
commit
d55be67df1
3 changed files with 111 additions and 47 deletions
|
|
@ -20,15 +20,13 @@ module Workers
|
||||||
DiasporaFederation::Salmon::InvalidAlgorithm,
|
DiasporaFederation::Salmon::InvalidAlgorithm,
|
||||||
DiasporaFederation::Salmon::InvalidEncoding,
|
DiasporaFederation::Salmon::InvalidEncoding,
|
||||||
Diaspora::Federation::AuthorIgnored,
|
Diaspora::Federation::AuthorIgnored,
|
||||||
|
Diaspora::Federation::InvalidAuthor,
|
||||||
# TODO: deprecated
|
# TODO: deprecated
|
||||||
DiasporaFederation::Salmon::MissingMagicEnvelope,
|
DiasporaFederation::Salmon::MissingMagicEnvelope,
|
||||||
DiasporaFederation::Salmon::MissingAuthor,
|
DiasporaFederation::Salmon::MissingAuthor,
|
||||||
DiasporaFederation::Salmon::MissingHeader,
|
DiasporaFederation::Salmon::MissingHeader,
|
||||||
DiasporaFederation::Salmon::InvalidHeader => e
|
DiasporaFederation::Salmon::InvalidHeader => e
|
||||||
logger.warn "don't retry for error: #{e.class}"
|
logger.warn "don't retry for error: #{e.class}"
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
|
||||||
logger.warn "failed to save received object: #{e.record.errors.full_messages}"
|
|
||||||
raise e unless e.message.include? "already been taken"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@ module Diaspora
|
||||||
# Raised, if author is ignored by the relayable parent author
|
# Raised, if author is ignored by the relayable parent author
|
||||||
class AuthorIgnored < RuntimeError
|
class AuthorIgnored < RuntimeError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Raised, if the author of the existing object doesn't match the received author
|
||||||
|
class InvalidAuthor < RuntimeError
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
module Diaspora
|
module Diaspora
|
||||||
module Federation
|
module Federation
|
||||||
module Receive
|
module Receive
|
||||||
|
extend Diaspora::Logging
|
||||||
|
|
||||||
def self.account_deletion(entity)
|
def self.account_deletion(entity)
|
||||||
AccountDeletion.new(
|
AccountDeletion.new(
|
||||||
person: author_of(entity),
|
person: author_of(entity),
|
||||||
|
|
@ -9,13 +11,16 @@ module Diaspora
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.comment(entity)
|
def self.comment(entity)
|
||||||
Comment.new(
|
author = author_of(entity)
|
||||||
author: author_of(entity),
|
ignore_existing_guid(Comment, entity.guid, author) do
|
||||||
guid: entity.guid,
|
Comment.new(
|
||||||
created_at: entity.created_at,
|
author: author,
|
||||||
text: entity.text,
|
guid: entity.guid,
|
||||||
commentable: Post.find_by(guid: entity.parent_guid)
|
created_at: entity.created_at,
|
||||||
).tap {|comment| save_relayable(comment, entity) }
|
text: entity.text,
|
||||||
|
commentable: Post.find_by(guid: entity.parent_guid)
|
||||||
|
).tap {|comment| save_relayable(comment, entity) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.contact(entity)
|
def self.contact(entity)
|
||||||
|
|
@ -31,29 +36,37 @@ module Diaspora
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.conversation(entity)
|
def self.conversation(entity)
|
||||||
Conversation.new(
|
author = author_of(entity)
|
||||||
author: author_of(entity),
|
try_load_existing_guid(Conversation, entity.guid, author) do
|
||||||
guid: entity.guid,
|
Conversation.new(
|
||||||
subject: entity.subject,
|
author: author,
|
||||||
created_at: entity.created_at,
|
guid: entity.guid,
|
||||||
participant_handles: entity.participants
|
subject: entity.subject,
|
||||||
).tap do |conversation|
|
created_at: entity.created_at,
|
||||||
conversation.messages = entity.messages.map {|message| build_message(message) }
|
participant_handles: entity.participants
|
||||||
conversation.save!
|
).tap do |conversation|
|
||||||
|
conversation.messages = entity.messages.map {|message| build_message(message) }
|
||||||
|
conversation.save!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.like(entity)
|
def self.like(entity)
|
||||||
Like.new(
|
author = author_of(entity)
|
||||||
author: author_of(entity),
|
ignore_existing_guid(Like, entity.guid, author) do
|
||||||
guid: entity.guid,
|
Like.new(
|
||||||
positive: entity.positive,
|
author: author,
|
||||||
target: entity.parent_type.constantize.find_by(guid: entity.parent_guid)
|
guid: entity.guid,
|
||||||
).tap {|like| save_relayable(like, entity) }
|
positive: entity.positive,
|
||||||
|
target: entity.parent_type.constantize.find_by(guid: entity.parent_guid)
|
||||||
|
).tap {|like| save_relayable(like, entity) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.message(entity)
|
def self.message(entity)
|
||||||
build_message(entity).tap(&:save!)
|
ignore_existing_guid(Message, entity.guid, author_of(entity)) do
|
||||||
|
build_message(entity).tap(&:save!)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.participation(entity)
|
def self.participation(entity)
|
||||||
|
|
@ -73,14 +86,17 @@ module Diaspora
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.poll_participation(entity)
|
def self.poll_participation(entity)
|
||||||
PollParticipation.new(
|
author = author_of(entity)
|
||||||
author: author_of(entity),
|
ignore_existing_guid(PollParticipation, entity.guid, author) do
|
||||||
guid: entity.guid,
|
PollParticipation.new(
|
||||||
poll: Poll.find_by(guid: entity.parent_guid)
|
author: author,
|
||||||
).tap do |poll_participation|
|
guid: entity.guid,
|
||||||
poll_participation.poll_answer_guid = entity.poll_answer_guid
|
poll: Poll.find_by(guid: entity.parent_guid)
|
||||||
|
).tap do |poll_participation|
|
||||||
|
poll_participation.poll_answer_guid = entity.poll_answer_guid
|
||||||
|
|
||||||
save_relayable(poll_participation, entity)
|
save_relayable(poll_participation, entity)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -115,27 +131,29 @@ module Diaspora
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.status_message(entity)
|
def self.status_message(entity)
|
||||||
StatusMessage.new(
|
author = author_of(entity)
|
||||||
author: author_of(entity),
|
try_load_existing_guid(StatusMessage, entity.guid, author) do
|
||||||
guid: entity.guid,
|
StatusMessage.new(
|
||||||
raw_message: entity.raw_message,
|
author: author,
|
||||||
public: entity.public,
|
guid: entity.guid,
|
||||||
created_at: entity.created_at,
|
raw_message: entity.raw_message,
|
||||||
provider_display_name: entity.provider_display_name
|
public: entity.public,
|
||||||
).tap do |status_message|
|
created_at: entity.created_at,
|
||||||
status_message.photos = entity.photos.map {|photo| build_photo(photo) }
|
provider_display_name: entity.provider_display_name
|
||||||
status_message.location = build_location(entity.location) if entity.location
|
).tap do |status_message|
|
||||||
status_message.poll = build_poll(entity.poll) if entity.poll
|
status_message.photos = entity.photos.map {|photo| build_photo(photo) }
|
||||||
|
status_message.location = build_location(entity.location) if entity.location
|
||||||
|
status_message.poll = build_poll(entity.poll) if entity.poll
|
||||||
|
|
||||||
status_message.save!
|
status_message.save!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def self.author_of(entity)
|
def self.author_of(entity)
|
||||||
Person.find_by(diaspora_handle: entity.author)
|
Person.find_by(diaspora_handle: entity.author)
|
||||||
end
|
end
|
||||||
|
private_class_method :author_of
|
||||||
|
|
||||||
def self.build_location(entity)
|
def self.build_location(entity)
|
||||||
Location.new(
|
Location.new(
|
||||||
|
|
@ -144,6 +162,7 @@ module Diaspora
|
||||||
lng: entity.lng
|
lng: entity.lng
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
private_class_method :build_location
|
||||||
|
|
||||||
def self.build_message(entity)
|
def self.build_message(entity)
|
||||||
Message.new(
|
Message.new(
|
||||||
|
|
@ -154,6 +173,7 @@ module Diaspora
|
||||||
conversation_guid: entity.conversation_guid
|
conversation_guid: entity.conversation_guid
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
private_class_method :build_message
|
||||||
|
|
||||||
def self.build_photo(entity)
|
def self.build_photo(entity)
|
||||||
Photo.new(
|
Photo.new(
|
||||||
|
|
@ -169,6 +189,7 @@ module Diaspora
|
||||||
width: entity.width
|
width: entity.width
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
private_class_method :build_photo
|
||||||
|
|
||||||
def self.build_poll(entity)
|
def self.build_poll(entity)
|
||||||
Poll.new(
|
Poll.new(
|
||||||
|
|
@ -183,6 +204,7 @@ module Diaspora
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
private_class_method :build_poll
|
||||||
|
|
||||||
def self.save_relayable(relayable, entity)
|
def self.save_relayable(relayable, entity)
|
||||||
retract_if_author_ignored(relayable)
|
retract_if_author_ignored(relayable)
|
||||||
|
|
@ -190,6 +212,7 @@ module Diaspora
|
||||||
relayable.author_signature = entity.author_signature if relayable.parent.author.local?
|
relayable.author_signature = entity.author_signature if relayable.parent.author.local?
|
||||||
relayable.save!
|
relayable.save!
|
||||||
end
|
end
|
||||||
|
private_class_method :save_relayable
|
||||||
|
|
||||||
def self.retract_if_author_ignored(relayable)
|
def self.retract_if_author_ignored(relayable)
|
||||||
parent_author = relayable.parent.author
|
parent_author = relayable.parent.author
|
||||||
|
|
@ -199,6 +222,45 @@ module Diaspora
|
||||||
|
|
||||||
raise Diaspora::Federation::AuthorIgnored
|
raise Diaspora::Federation::AuthorIgnored
|
||||||
end
|
end
|
||||||
|
private_class_method :retract_if_author_ignored
|
||||||
|
|
||||||
|
# check if the object already exists, otherwise save it.
|
||||||
|
# if save fails (probably because of a second object received parallel),
|
||||||
|
# check again if an object with the same guid already exists, but maybe has a different author.
|
||||||
|
# @raise [InvalidAuthor] if the author of the existing object doesn't match
|
||||||
|
def self.ignore_existing_guid(klass, guid, author)
|
||||||
|
yield unless klass.where(guid: guid, author_id: author.id).exists?
|
||||||
|
rescue => e
|
||||||
|
raise e unless load_from_database(klass, guid, author)
|
||||||
|
logger.warn "ignoring error on receive #{klass}:#{guid}: #{e.class}: #{e.message}"
|
||||||
|
end
|
||||||
|
private_class_method :ignore_existing_guid
|
||||||
|
|
||||||
|
# try to load the object first from the DB and if not available, save it.
|
||||||
|
# if save fails (probably because of a second object received parallel),
|
||||||
|
# try again to load it, because it is possibly there now.
|
||||||
|
# @raise [InvalidAuthor] if the author of the existing object doesn't match
|
||||||
|
def self.try_load_existing_guid(klass, guid, author)
|
||||||
|
load_from_database(klass, guid, author) || yield
|
||||||
|
rescue Diaspora::Federation::InvalidAuthor => e
|
||||||
|
raise e # don't try loading from db twice
|
||||||
|
rescue => e
|
||||||
|
logger.warn "failed to save #{klass}:#{guid} (#{e.class}: #{e.message}) - try loading it from DB"
|
||||||
|
load_from_database(klass, guid, author).tap do |object|
|
||||||
|
raise e unless object
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :try_load_existing_guid
|
||||||
|
|
||||||
|
# @raise [InvalidAuthor] if the author of the loaded object doesn't match
|
||||||
|
def self.load_from_database(klass, guid, author)
|
||||||
|
klass.find_by(guid: guid).tap do |object|
|
||||||
|
if object && object.author_id != author.id
|
||||||
|
raise Diaspora::Federation::InvalidAuthor, "#{klass}:#{guid}: #{author.diaspora_handle}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :load_from_database
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue