couchrest_changes 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@ require 'fileutils'
3
3
  require 'pathname'
4
4
 
5
5
  require 'couchrest/changes/config'
6
+ require 'couchrest/changes/database_proxy'
6
7
  require 'couchrest/changes/observer'
7
8
 
8
9
  module CouchRest
@@ -0,0 +1,41 @@
1
+ module CouchRest::Changes
2
+
3
+ #
4
+ # CouchRest uses curl for 'streaming' requests
5
+ # (requests with a block passed to the db).
6
+ #
7
+ # Unfortunately, this leaks the username and password in the process list.
8
+ # We don't want to do this. So, we create two separate CouchRest::Database
9
+ # instances: one that is for normal requests and one that is used for
10
+ # streaming requests. The streaming one we hack to use netrc file in order
11
+ # to keep authentication info out of the process list.
12
+ #
13
+ # If no netrc file is configure, then this DatabaseProxy just uses the
14
+ # regular db.
15
+ #
16
+ class DatabaseProxy
17
+ def initialize(db_name)
18
+ @db = CouchRest.new(Config.couch_host).database(db_name)
19
+ unless @db
20
+ Config.logger.error { "Database #{db_name} not found!" }
21
+ raise RuntimeError "Database #{db_name} not found!"
22
+ end
23
+ if Config.connection[:netrc] && !Config.connection[:netrc].empty?
24
+ @db_stream = CouchRest.new(Config.couch_host_no_auth).database(db_name)
25
+ streamer = @db_stream.instance_variable_get('@streamer') # cheating, not exposed.
26
+ streamer.default_curl_opts += " --netrc-file \"#{Config.connection[:netrc]}\""
27
+ else
28
+ @db_stream = @db
29
+ end
30
+ end
31
+
32
+ def changes(*args, &block)
33
+ if block
34
+ @db_stream.changes(*args, &block)
35
+ else
36
+ @db.changes(*args)
37
+ end
38
+ end
39
+ end
40
+
41
+ end
@@ -1,43 +1,5 @@
1
1
  module CouchRest::Changes
2
2
 
3
- #
4
- # CouchRest uses curl for 'streaming' requests
5
- # (requests with a block passed to the db).
6
- #
7
- # Unfortunately, this leaks the username and password in the process list.
8
- # We don't want to do this. So, we create two separate CouchRest::Database
9
- # instances: one that is for normal requests and one that is used for
10
- # streaming requests. The streaming one we hack to use netrc file in order
11
- # to keep authentication info out of the process list.
12
- #
13
- # If no netrc file is configure, then this DatabaseProxy just uses the
14
- # regular db.
15
- #
16
- class DatabaseProxy
17
- def initialize(db_name)
18
- @db = CouchRest.new(Config.couch_host).database(db_name)
19
- unless @db
20
- Config.logger.error "Database #{db_name} not found!"
21
- raise RuntimeError "Database #{db_name} not found!"
22
- end
23
- if Config.connection[:netrc] && !Config.connection[:netrc].empty?
24
- @db_stream = CouchRest.new(Config.couch_host_no_auth).database(db_name)
25
- streamer = @db_stream.instance_variable_get('@streamer') # cheating, not exposed.
26
- streamer.default_curl_opts += " --netrc-file \"#{Config.connection[:netrc]}\""
27
- else
28
- @db_stream = @db
29
- end
30
- end
31
-
32
- def changes(*args, &block)
33
- if block
34
- @db_stream.changes(*args, &block)
35
- else
36
- @db.changes(*args)
37
- end
38
- end
39
- end
40
-
41
3
  #
42
4
  # NOTE: the sequence stored in the variable @since is a different format
43
5
  # depending on which flavor of couchdb is being used. For normal couchdb
@@ -51,13 +13,13 @@ module CouchRest::Changes
51
13
 
52
14
  def initialize(db_name, options = {})
53
15
  @db_name = Config.complete_db_name(db_name)
54
- info "Tracking #{db_name}"
55
- debug "Options: #{options.inspect}" if options.keys.any?
16
+ info { "Tracking #{db_name}" }
17
+ debug { "Options: #{options.inspect}" } if options.keys.any?
56
18
  @options = options
57
19
  @db = DatabaseProxy.new(@db_name)
58
20
  setup_sequence_file(@db_name)
59
21
  unless rerun?
60
- @since = read_seq(@db_name)
22
+ @since = read_or_reset_sequence(@db_name)
61
23
  else
62
24
  @since = 0
63
25
  end
@@ -84,8 +46,7 @@ module CouchRest::Changes
84
46
  end
85
47
 
86
48
  def listen
87
- info "listening..."
88
- debug "Starting at sequence #{since}"
49
+ info { "Listening to #{@db_name}/_changes starting at sequence #{since}" }
89
50
  last = nil
90
51
  result = @db.changes(feed_options) do |hash|
91
52
  last = hash
@@ -97,9 +58,9 @@ module CouchRest::Changes
97
58
  # appearently MultiJson has issues with the end of the couch stream.
98
59
  # So sometimes we get a MultiJson::LoadError instead...
99
60
  rescue MultiJson::LoadError, EOFError, RestClient::ServerBrokeConnection => exc
100
- error "Couch stream ended - #{exc.class}"
101
- debug result.inspect if result
102
- debug last.inspect if last
61
+ error { "Couch #{@db_name}/_changes stream ended - #{exc.class}" }
62
+ debug { result.inspect } if result
63
+ debug { last.inspect } if last
103
64
  retry if retry_without_sequence?(result, last) || retry_later?
104
65
  end
105
66
 
@@ -156,6 +117,25 @@ module CouchRest::Changes
156
117
  end
157
118
  end
158
119
 
120
+ #
121
+ # if the sequence in the database is newer than the sequence stored
122
+ # in the sequence file, then we need to reset the stored sequence
123
+ # to what is in the database.
124
+ #
125
+ def read_or_reset_sequence(db_name)
126
+ sequence = read_seq(db_name)
127
+ if sequence != 0
128
+ seq_number = parse_sequence_number(sequence)
129
+ seq_number_in_db = parse_sequence_number(last_sequence)
130
+ if seq_number_in_db < seq_number
131
+ info { "Stored sequence (#{seq_number}) is greater than in db (#{seq_number_in_db}), resetting sequence to 0." }
132
+ sequence = 0
133
+ store_seq(db_name, sequence)
134
+ end
135
+ end
136
+ return sequence
137
+ end
138
+
159
139
  #
160
140
  # reads the sequence file, e.g. (/var/run/tapicero/users.seq), returning
161
141
  # the sequence number or zero if the sequence number could not be
@@ -163,17 +143,17 @@ module CouchRest::Changes
163
143
  #
164
144
  def read_seq(db_name)
165
145
  filename = sequence_file_name(db_name)
166
- debug "Looking up sequence here: #{filename}"
146
+ debug { "Looking up sequence here: #{filename}" }
167
147
  result = File.read(filename)
168
148
  if result.empty?
169
- debug "Found no sequence in the file #{filename}."
149
+ debug { "Found no sequence in the file #{filename}." }
170
150
  return 0
171
151
  else
172
- debug "Found sequence: #{result}"
152
+ debug { "Found sequence: #{result}" }
173
153
  return result
174
154
  end
175
155
  rescue Errno::ENOENT => e
176
- warn "No sequence file found. Starting from scratch (#{filename})"
156
+ warn { "No sequence file found. Starting from scratch (#{filename})" }
177
157
  return 0
178
158
  end
179
159
 
@@ -189,7 +169,7 @@ module CouchRest::Changes
189
169
  def retry_without_sequence?(result, last_hash)
190
170
  if malformated_sequence?(result) || malformated_sequence?(last_hash)
191
171
  @since = 0
192
- info "Trying to start from scratch (db #{@db_name})."
172
+ info { "Trying to start from scratch (db #{@db_name})." }
193
173
  debug { {:result => result, :last_hash => last_hash}.inspect }
194
174
  end
195
175
  end
@@ -199,14 +179,29 @@ module CouchRest::Changes
199
179
  reason && ( reason.include?('since') || reason == 'badarg' )
200
180
  end
201
181
 
182
+ #
183
+ # Sequence might be a number, a number as a string, an array,
184
+ # or an array encoded as a string. This method returns the
185
+ # integer value of the number part of the sequence.
186
+ #
187
+ def parse_sequence_number(seq)
188
+ if seq.is_a? String
189
+ seq = MultiJson.decode(seq)
190
+ end
191
+ [seq].flatten.first.to_i
192
+ rescue Exception => exc
193
+ error { "Failed to parse sequence #{seq.inspect} (#{exc})." }
194
+ 0
195
+ end
196
+
202
197
  def hash_for_change?(hash)
203
198
  hash["id"] && hash["changes"]
204
199
  end
205
200
 
206
201
  def retry_later?
207
202
  return unless rerun?
208
- info "Will retry in 15 seconds."
209
- info "Retried #{retry_count} times so far."
203
+ info { "Will retry in 15 seconds." }
204
+ info { "Retried #{retry_count} times so far." }
210
205
  sleep 15
211
206
  @retry_count += 1
212
207
  end
@@ -1,5 +1,5 @@
1
1
  module CouchRest
2
2
  module Changes
3
- VERSION = "0.2.1"
3
+ VERSION = "0.2.2"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: couchrest_changes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-04-01 00:00:00.000000000 Z
12
+ date: 2015-04-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: couchrest
@@ -133,6 +133,7 @@ extra_rdoc_files: []
133
133
  files:
134
134
  - lib/couchrest/changes.rb
135
135
  - lib/couchrest/changes/config.rb
136
+ - lib/couchrest/changes/database_proxy.rb
136
137
  - lib/couchrest/changes/observer.rb
137
138
  - lib/couchrest/changes/version.rb
138
139
  - Rakefile