hootenanny 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +5 -7
- data/Gemfile.lock +46 -28
- data/app/controllers/hootenanny/notifications_controller.rb +31 -0
- data/app/controllers/hootenanny/parameters.rb +16 -0
- data/app/controllers/hootenanny/subscriptions_controller.rb +24 -3
- data/app/models/hootenanny/publish_notification.rb +90 -0
- data/app/models/hootenanny/subscription.rb +72 -23
- data/config/routes.rb +2 -1
- data/db/migrate/20130607182642_add_started_at_and_lease_duration_to_subscriptions.rb +15 -0
- data/db/migrate/20130608225621_add_hmac_secret_to_subscription.rb +5 -0
- data/db/migrate/20130611235218_add_publish_notifications.rb +9 -0
- data/db/migrate/20130612153138_add_timestamps_to_publish_notification.rb +5 -0
- data/db/migrate/20130705200729_add_processed_flag_to_publish_notifications.rb +6 -0
- data/db/migrate/20130711061329_switch_publish_notification_process_state_from_boolean_to_string.rb +11 -0
- data/db/migrate/20130711061558_add_index_to_notifications_state.rb +5 -0
- data/hootenanny.gemspec +6 -2
- data/lib/hootenanny/configuration.rb +43 -0
- data/lib/hootenanny/correspondent.rb +44 -0
- data/lib/hootenanny/errors.rb +42 -1
- data/lib/hootenanny/feed.rb +75 -0
- data/lib/hootenanny/feed/atom_feed.rb +18 -0
- data/lib/hootenanny/feed/atom_feed_item.rb +8 -0
- data/lib/hootenanny/feed/digest_feed.rb +14 -0
- data/lib/hootenanny/feed/digest_feed_item.rb +11 -0
- data/lib/hootenanny/feed/feed_item.rb +30 -0
- data/lib/hootenanny/feed/file.rb +66 -0
- data/lib/hootenanny/feed/json_feed.rb +48 -0
- data/lib/hootenanny/feed/json_feed_item.rb +8 -0
- data/lib/hootenanny/feed/null_feed.rb +27 -0
- data/lib/hootenanny/feed/rss_feed.rb +52 -0
- data/lib/hootenanny/feed/rss_feed_item.rb +8 -0
- data/lib/hootenanny/feed_store.rb +30 -0
- data/lib/hootenanny/feed_store/file_feed_store.rb +55 -0
- data/lib/hootenanny/feed_store/web_feed_store.rb +42 -0
- data/lib/hootenanny/hub.rb +116 -22
- data/lib/hootenanny/publish_notification_expiration_policy.rb +17 -0
- data/lib/hootenanny/request.rb +40 -0
- data/lib/hootenanny/request/publish_notification.rb +94 -0
- data/lib/hootenanny/request/subscription.rb +153 -0
- data/lib/hootenanny/subscription_delivery.rb +47 -0
- data/lib/hootenanny/topic.rb +38 -0
- data/lib/hootenanny/topic_synchronizer.rb +71 -0
- data/lib/hootenanny/uri.rb +53 -0
- data/lib/hootenanny/verification.rb +108 -0
- data/lib/hootenanny/version.rb +1 -1
- data/spec/dummy/config/database.yml +12 -6
- data/spec/dummy/db/migrate/20130607183149_add_started_at_and_lease_duration_to_subscriptions.hootenanny.rb +16 -0
- data/spec/dummy/db/migrate/20130608231253_add_hmac_secret_to_subscription.hootenanny.rb +6 -0
- data/spec/dummy/db/migrate/20130611235546_add_publish_notifications.hootenanny.rb +10 -0
- data/spec/dummy/db/migrate/20130612153353_add_timestamps_to_publish_notification.hootenanny.rb +6 -0
- data/spec/dummy/db/migrate/20130705200832_add_processed_flag_to_publish_notifications.hootenanny.rb +7 -0
- data/spec/dummy/db/migrate/20130711061518_switch_publish_notification_process_state_from_boolean_to_string.hootenanny.rb +12 -0
- data/spec/dummy/db/migrate/20130711061629_add_index_to_notifications_state.hootenanny.rb +6 -0
- data/spec/factories/publish_notification.rb +13 -0
- data/spec/factories/requests/publish_notification.rb +11 -0
- data/spec/factories/requests/subscription.rb +46 -0
- data/spec/factories/subscription.rb +14 -2
- data/spec/factories/verification.rb +15 -0
- data/spec/features/publishers/can_notify_the_hub_of_content_updates_spec.rb +58 -0
- data/spec/features/subscribers/are_protected_from_unwarranted_subscriptions_spec.rb +59 -0
- data/spec/features/subscribers/can_receive_distributions_of_topic_content_spec.rb +175 -0
- data/spec/features/subscribers/can_subscribe_to_a_topic_spec.rb +76 -7
- data/spec/fixtures/feeds/atom/97d79220f68b4bf27.atom +0 -0
- data/spec/fixtures/feeds/atom/sample_feed.atom +51 -0
- data/spec/fixtures/feeds/digest/97d79220f68b4bf27.digest +1 -0
- data/spec/fixtures/feeds/digest/complete_broadcasted_items/5b187098da59f077f/97d79220f68b4bf27.digest +6 -0
- data/spec/fixtures/feeds/digest/incomplete_broadcasted_items/5b187098da59f077f/97d79220f68b4bf27.digest +5 -0
- data/spec/fixtures/feeds/digest/sample_feed.digest +6 -0
- data/spec/fixtures/feeds/json/97d79220f68b4bf27.json +1 -0
- data/spec/fixtures/feeds/json/feed_with_one_item.json +21 -0
- data/spec/fixtures/feeds/json/sample_feed.json +30 -0
- data/spec/fixtures/feeds/rss/5b187098da59f077f/97d79220f68b4bf27.rss +21 -0
- data/spec/fixtures/feeds/rss/97d79220f68b4bf27.rss +0 -0
- data/spec/fixtures/feeds/rss/feed_with_one_item.rss +15 -0
- data/spec/fixtures/feeds/rss/minimal_feed.rss +12 -0
- data/spec/fixtures/feeds/rss/sample_feed.rss +22 -0
- data/spec/fixtures/feeds/rss/sample_feed_2.rss +22 -0
- data/spec/lib/hootenanny/configuration_spec.rb +7 -0
- data/spec/lib/hootenanny/correspondent_spec.rb +94 -0
- data/spec/lib/hootenanny/errors_spec.rb +21 -0
- data/spec/lib/hootenanny/feed/atom_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/atom_feed_spec.rb +40 -0
- data/spec/lib/hootenanny/feed/digest_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/digest_feed_spec.rb +40 -0
- data/spec/lib/hootenanny/feed/feed_item_spec.rb +49 -0
- data/spec/lib/hootenanny/feed/file_spec.rb +66 -0
- data/spec/lib/hootenanny/feed/json_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/json_feed_spec.rb +128 -0
- data/spec/lib/hootenanny/feed/null_feed_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/rss_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/rss_feed_spec.rb +143 -0
- data/spec/lib/hootenanny/feed_spec.rb +159 -0
- data/spec/lib/hootenanny/feed_store/file_feed_store_spec.rb +58 -0
- data/spec/lib/hootenanny/feed_store/web_feed_store_spec.rb +47 -0
- data/spec/lib/hootenanny/feed_store_spec.rb +27 -0
- data/spec/lib/hootenanny/hub_spec.rb +73 -0
- data/spec/lib/hootenanny/publish_notification_expiration_policy_spec.rb +35 -0
- data/spec/lib/hootenanny/request/publish_notification_spec.rb +43 -0
- data/spec/lib/hootenanny/request/subscription_spec.rb +89 -0
- data/spec/lib/hootenanny/request_spec.rb +21 -0
- data/spec/lib/hootenanny/subscription_delivery_spec.rb +54 -0
- data/spec/lib/hootenanny/topic_spec.rb +15 -0
- data/spec/lib/hootenanny/topic_synchronizer_spec.rb +98 -0
- data/spec/lib/hootenanny/uri_spec.rb +32 -0
- data/spec/lib/hootenanny/verification_spec.rb +92 -0
- data/spec/models/hootenanny/publish_notification_spec.rb +55 -0
- data/spec/models/hootenanny/subscription_spec.rb +58 -19
- data/spec/support/verification.rb +63 -0
- metadata +231 -14
- data/spec/controllers/hootenanny/hub_spec.rb +0 -15
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require 'hootenanny/request/subscription'
|
3
|
+
require 'hootenanny/request/publish_notification'
|
4
|
+
|
5
|
+
module Hootenanny
|
6
|
+
class Request
|
7
|
+
attr_accessor :type
|
8
|
+
|
9
|
+
###
|
10
|
+
# Private: Builds a specific request object based on the options passed in.
|
11
|
+
#
|
12
|
+
# options - A Hash of options representing the request that will be built. The
|
13
|
+
# only option that is required for _this_ method is :type, however
|
14
|
+
# depending on the class that is to be built, it may require other
|
15
|
+
# options. See each class in turn for details.
|
16
|
+
#
|
17
|
+
# :type - The type of request that should be built. Currently, valid
|
18
|
+
# opitons are:
|
19
|
+
#
|
20
|
+
# * :subscription
|
21
|
+
#
|
22
|
+
# Examples:
|
23
|
+
#
|
24
|
+
# Hootenanny::Request.build(type: :subscription,
|
25
|
+
# callback: 'http://example.com',)
|
26
|
+
# # => <Hootenanny::Request::Subscription>
|
27
|
+
#
|
28
|
+
# Returns an indeterminate type of object based on the :type option
|
29
|
+
# Raises Hootenanny::Request::BuildError if there is a problem
|
30
|
+
#
|
31
|
+
def self.build(options = {})
|
32
|
+
type = options.fetch(:type).to_s.camelize
|
33
|
+
klass = "Hootenanny::Request::#{type}".constantize
|
34
|
+
|
35
|
+
klass.build options
|
36
|
+
rescue KeyError => e
|
37
|
+
raise Hootenanny::Request::BuildError.wrap(e)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
2
|
+
require 'hootenanny/request'
|
3
|
+
require 'hootenanny/publish_notification'
|
4
|
+
|
5
|
+
module Hootenanny
|
6
|
+
class Request
|
7
|
+
class PublishNotification < Hootenanny::Request
|
8
|
+
|
9
|
+
attr_reader :topics
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
self.topics = options.fetch(:topics)
|
13
|
+
rescue KeyError => e
|
14
|
+
raise Hootenanny::Request::BuildError.wrap(e)
|
15
|
+
end
|
16
|
+
|
17
|
+
###
|
18
|
+
# Private: Builds a publication request object based on the options passed in.
|
19
|
+
#
|
20
|
+
# options - A Hash of options representing the publish notification request
|
21
|
+
# that will be built.
|
22
|
+
#
|
23
|
+
# :topics - A String or Array representing the topics which have
|
24
|
+
# been proposed to have been updated.
|
25
|
+
#
|
26
|
+
# Examples:
|
27
|
+
#
|
28
|
+
# # When more than one topic is passed in
|
29
|
+
#
|
30
|
+
# Hootenanny::Request::PublishNotification.build(topics: [
|
31
|
+
# 'http://another.org',
|
32
|
+
# 'http://other.com'
|
33
|
+
# ])
|
34
|
+
# # => <Hootenanny::Request::PublishNotification topics: [
|
35
|
+
# 'http://another.org',
|
36
|
+
# 'http://other.com'
|
37
|
+
# ]>
|
38
|
+
#
|
39
|
+
#
|
40
|
+
# # When only one topic is passed in
|
41
|
+
#
|
42
|
+
# Hootenanny::Request::PublishNotification.build(topics: 'http://another.org')
|
43
|
+
# # => <Hootenanny::Request::Publish topics: ['http://another.org']>
|
44
|
+
#
|
45
|
+
# Returns a Hootenanny::Request::PublishNotification
|
46
|
+
# Raises Hootenanny::Request::BuildError if there is a problem
|
47
|
+
#
|
48
|
+
def self.build(options = {})
|
49
|
+
options = normalize_build_options(options)
|
50
|
+
request = allocate
|
51
|
+
|
52
|
+
request.send(:initialize, options)
|
53
|
+
|
54
|
+
request
|
55
|
+
end
|
56
|
+
|
57
|
+
###
|
58
|
+
# Private: Generates one ore more (one for each topic in the request) records
|
59
|
+
# of the publish notification to be consumed at a later time by the hub. If
|
60
|
+
# the hub has already been notified of the content update, a second item is
|
61
|
+
# not created but rather the original one is retrieved and returned.
|
62
|
+
#
|
63
|
+
# Example:
|
64
|
+
#
|
65
|
+
# Hootenanny::Request::PublishNotification.build(topics: 'http://another.org')
|
66
|
+
#
|
67
|
+
# publish_notification_request.apply
|
68
|
+
#
|
69
|
+
# # => <Hootenanny::PublishNotification topic: 'http://another.org'>
|
70
|
+
#
|
71
|
+
# Returns a Hootenanny::PublishNotification if the request is verified
|
72
|
+
# Raises Hootenanny::PublishNotificationError if there is a problem
|
73
|
+
#
|
74
|
+
def apply
|
75
|
+
topics.map do |topic|
|
76
|
+
Hootenanny::PublishNotification.notify(topic)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
attr_writer :topics
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def self.normalize_build_options(options)
|
87
|
+
options[:topics] = options[:url] if options[:url]
|
88
|
+
options[:topics] = Array.wrap(options[:topics]) if options[:topics]
|
89
|
+
|
90
|
+
options
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'hootenanny/request'
|
2
|
+
require 'hootenanny/subscription'
|
3
|
+
require 'hootenanny/verification'
|
4
|
+
|
5
|
+
module Hootenanny
|
6
|
+
class Request
|
7
|
+
class Subscription < Hootenanny::Request
|
8
|
+
|
9
|
+
attr_reader :subscriber,
|
10
|
+
:topic,
|
11
|
+
:lease_duration,
|
12
|
+
:requester_token,
|
13
|
+
:digest_secret,
|
14
|
+
:verification_class
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
self.subscriber = options.fetch(:subscriber)
|
18
|
+
self.topic = options.fetch(:topic)
|
19
|
+
self.lease_duration = options.fetch(:lease_duration)
|
20
|
+
self.requester_token = options.fetch(:requester_token, nil)
|
21
|
+
self.digest_secret = options.fetch(:secret, nil)
|
22
|
+
self.verification_class = options.fetch(:verification_class,
|
23
|
+
Hootenanny::Verification)
|
24
|
+
rescue KeyError => e
|
25
|
+
raise Hootenanny::Request::BuildError.wrap(e)
|
26
|
+
end
|
27
|
+
|
28
|
+
###
|
29
|
+
# Private: Builds a subscription request object based on the options passed
|
30
|
+
# in.
|
31
|
+
#
|
32
|
+
# options - A Hash of options representing the subscription request that will
|
33
|
+
# be built.
|
34
|
+
#
|
35
|
+
# :subscriber - A String or Ruby URI representing the URI which
|
36
|
+
# the topic updates will be sent to.
|
37
|
+
# :topic - A String or Ruby URI representing the URI which
|
38
|
+
# publish the activities for a given topic.
|
39
|
+
# :requester_token - A String token sent from the requester which
|
40
|
+
# they can use to identify the request on their
|
41
|
+
# system (optional).
|
42
|
+
# :digest_secret - A String token of no more than 200 bytes which
|
43
|
+
# will be used during content distribution so
|
44
|
+
# that the subscriber can verify that the request
|
45
|
+
# is from the hub and not a undesired 3rd party
|
46
|
+
# (optional).
|
47
|
+
# :lease_duration - A Number representing the amount of time after
|
48
|
+
# the subscription is created during which time
|
49
|
+
# the subscription will be active. This is
|
50
|
+
# a _request_ and may be overridden (optional).
|
51
|
+
#
|
52
|
+
# Examples:
|
53
|
+
#
|
54
|
+
# Hootenanny::Request::Subscription.build(callback: 'http://example.com',
|
55
|
+
# topic: 'http://another.org')
|
56
|
+
#
|
57
|
+
# # => <Hootenanny::Request::Subscription subscriber: 'http://example.com',
|
58
|
+
# topic: 'http://another.org'>
|
59
|
+
#
|
60
|
+
# Returns a Hootenanny::Request::Subscription
|
61
|
+
# Raises Hootenanny::Request::BuildError if there is a problem
|
62
|
+
#
|
63
|
+
def self.build(options = {})
|
64
|
+
options = normalize_build_options(options)
|
65
|
+
request = allocate
|
66
|
+
|
67
|
+
request.send(:initialize, options)
|
68
|
+
|
69
|
+
request
|
70
|
+
end
|
71
|
+
|
72
|
+
###
|
73
|
+
# Private: Applies the subscription that the request is asking for. It does
|
74
|
+
# this in an idempotent way. If the subscription being requested already
|
75
|
+
# exists, it is not modified but is instead retrieved and returned.
|
76
|
+
#
|
77
|
+
# The request itself must be verified prior to the request being applied. An
|
78
|
+
# unverifiable request will raise a Hootenanny::SubscriptionError.
|
79
|
+
#
|
80
|
+
# Example:
|
81
|
+
#
|
82
|
+
# # If the request for the subscription is verified
|
83
|
+
#
|
84
|
+
# subscription_request = Hootenanny::Request::Subscription.build(
|
85
|
+
# callback: 'http://example.com',
|
86
|
+
# topic: 'http://another.org')
|
87
|
+
#
|
88
|
+
# subscription_request.apply
|
89
|
+
#
|
90
|
+
# # => <Hootenanny::Subscription subscriber: 'http://example.com',
|
91
|
+
# topic: 'http://another.org'>
|
92
|
+
#
|
93
|
+
#
|
94
|
+
#
|
95
|
+
# # If the request for the subscription is unverifiable
|
96
|
+
#
|
97
|
+
# subscription_request = Hootenanny::Request::Subscription.build(
|
98
|
+
# callback: 'http://iamahacker.com',
|
99
|
+
# topic: 'http://thisdoesnotexist.org')
|
100
|
+
#
|
101
|
+
# subscription_request.apply
|
102
|
+
#
|
103
|
+
# # => <Hootenanny::SubscriptionError>
|
104
|
+
#
|
105
|
+
# Returns a Hootenanny::Subscription if the request is verified
|
106
|
+
# Raises Hootenanny::SubscriptionError if there is a problem
|
107
|
+
#
|
108
|
+
def apply
|
109
|
+
raise Hootenanny::SubscriptionError.new('Request could not be verified.') unless verified?
|
110
|
+
|
111
|
+
Hootenanny::Subscription.subscribe( subscriber: subscriber,
|
112
|
+
to: topic,
|
113
|
+
lease_duration: lease_duration,
|
114
|
+
digest_secret: digest_secret)
|
115
|
+
end
|
116
|
+
|
117
|
+
def verified?
|
118
|
+
verification.verified?
|
119
|
+
end
|
120
|
+
|
121
|
+
def verification
|
122
|
+
@verification ||= verification_class.new(
|
123
|
+
url: subscriber,
|
124
|
+
mode: :subscribe,
|
125
|
+
topic: topic,
|
126
|
+
requester_token: requester_token,
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
protected
|
131
|
+
|
132
|
+
attr_writer :subscriber,
|
133
|
+
:topic,
|
134
|
+
:lease_duration,
|
135
|
+
:requester_token,
|
136
|
+
:digest_secret,
|
137
|
+
:verification_class
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def self.normalize_build_options(options)
|
142
|
+
options[:subscriber] = options[:callback] if options.has_key?(:callback)
|
143
|
+
options[:lease_duration] = options[:lease_seconds] if options.has_key?(:lease_seconds)
|
144
|
+
|
145
|
+
options[:lease_duration] = options[:lease_duration] ?
|
146
|
+
options[:lease_duration].to_i :
|
147
|
+
1_577_880_000 # 50 Year Duration = 60 * 60 * 24 * 365.25 * 50
|
148
|
+
|
149
|
+
options
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
module Hootenanny
|
5
|
+
class SubscriptionDelivery
|
6
|
+
def initialize(options = {})
|
7
|
+
self.subscriber_url = options.fetch(:subscriber)
|
8
|
+
self.digest_secret = options.fetch(:digest_secret, nil)
|
9
|
+
self.feed = options.fetch(:feed)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.deliver(options = {})
|
13
|
+
delivery = allocate
|
14
|
+
delivery.send(:initialize, options)
|
15
|
+
|
16
|
+
delivery.deliver
|
17
|
+
end
|
18
|
+
|
19
|
+
def deliver
|
20
|
+
self.response = Faraday.post subscriber_url.to_s do |request|
|
21
|
+
request.headers['Content-Type'] = feed.content_type
|
22
|
+
request.headers['X-Hub-Signature'] = "sha1=#{signature}" unless signature == ''
|
23
|
+
request.body = feed.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
self.successful = response.success?
|
27
|
+
end
|
28
|
+
|
29
|
+
def successful?
|
30
|
+
successful
|
31
|
+
end
|
32
|
+
|
33
|
+
def signature
|
34
|
+
return '' if digest_secret == '' or digest_secret.nil?
|
35
|
+
|
36
|
+
@signature ||= OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('SHA'), digest_secret, feed.to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
attr_accessor :subscriber_url,
|
42
|
+
:digest_secret,
|
43
|
+
:feed,
|
44
|
+
:response,
|
45
|
+
:successful
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'hootenanny/uri'
|
2
|
+
|
3
|
+
module Hootenanny
|
4
|
+
class Topic
|
5
|
+
|
6
|
+
attr_reader :url
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
self.url = Hootenanny::URI.parse(options.fetch(:url))
|
10
|
+
end
|
11
|
+
|
12
|
+
###
|
13
|
+
# Private: Creates a topic from a String representing the URL of the topic
|
14
|
+
#
|
15
|
+
# url - A String representing the URL of the topic
|
16
|
+
#
|
17
|
+
# Examples:
|
18
|
+
#
|
19
|
+
# Hootenanny::Topic.from_url('http://example.com')
|
20
|
+
#
|
21
|
+
# Returns a Hootenanny::Topic
|
22
|
+
# Raises a Hootenanny::URI::InvalidError if the URI cannot be parsed
|
23
|
+
# Raises a Hootenanny::URI::InvalidSchemeError if the URI is not either HTTP
|
24
|
+
# or HTTPS
|
25
|
+
#
|
26
|
+
def self.from_url(url, options = {})
|
27
|
+
topic = allocate
|
28
|
+
|
29
|
+
topic.send(:initialize, options.merge(:url => url))
|
30
|
+
|
31
|
+
topic
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
attr_writer :url
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'hootenanny/configuration'
|
2
|
+
|
3
|
+
module Hootenanny
|
4
|
+
class TopicSynchronizer
|
5
|
+
def initialize(options = {})
|
6
|
+
self.subscriber_url = options.fetch(:subscriber)
|
7
|
+
self.topic_url = options.fetch(:topic)
|
8
|
+
self.digest_secret = options.fetch(:digest_secret, nil)
|
9
|
+
|
10
|
+
self.local_feed_store = options[:local_feed_store] ||
|
11
|
+
Hootenanny.config.default_local_feed_store
|
12
|
+
self.remote_feed_store = options[:remote_feed_store] ||
|
13
|
+
Hootenanny.config.default_remote_feed_store
|
14
|
+
self.subscription_delivery = options[:subscription_delivery] ||
|
15
|
+
Hootenanny.config.default_subscription_delivery
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.sync(options = {})
|
19
|
+
self.new(options).sync
|
20
|
+
end
|
21
|
+
|
22
|
+
def sync
|
23
|
+
return true if unsynchronized_feed.empty?
|
24
|
+
|
25
|
+
if subscription_delivery.deliver( feed: unsynchronized_feed,
|
26
|
+
url: topic_url,
|
27
|
+
subscriber: subscriber_url)
|
28
|
+
|
29
|
+
local_feed_store.store( feed: remote_feed.to_digest_feed,
|
30
|
+
url: topic_url,
|
31
|
+
type: 'digest',
|
32
|
+
path: subscriber_url.to_digest)
|
33
|
+
else
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
attr_accessor :subscriber_url,
|
41
|
+
:topic_url,
|
42
|
+
:digest_secret,
|
43
|
+
:local_feed_store,
|
44
|
+
:remote_feed_store,
|
45
|
+
:subscription_delivery
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def unsynchronized_feed
|
50
|
+
remote_feed - local_feed
|
51
|
+
end
|
52
|
+
|
53
|
+
def subscriber_url=(other)
|
54
|
+
@subscriber_url = Hootenanny::URI.parse(other)
|
55
|
+
end
|
56
|
+
|
57
|
+
def topic_url=(other)
|
58
|
+
@topic_url = Hootenanny::URI.parse(other)
|
59
|
+
end
|
60
|
+
|
61
|
+
def local_feed
|
62
|
+
local_feed_store.fetch(url: topic_url,
|
63
|
+
path: subscriber_url.to_digest)
|
64
|
+
end
|
65
|
+
|
66
|
+
def remote_feed
|
67
|
+
remote_feed_store.fetch(url: topic_url,
|
68
|
+
path: subscriber_url.to_digest)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|