rets 0.9.0 → 0.10.0

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/Manifest.txt +24 -0
  4. data/README.md +73 -1
  5. data/Rakefile +1 -1
  6. data/lib/rets.rb +202 -1
  7. data/lib/rets/client.rb +83 -94
  8. data/lib/rets/http_client.rb +42 -0
  9. data/lib/rets/metadata.rb +15 -3
  10. data/lib/rets/metadata/caching.rb +59 -0
  11. data/lib/rets/metadata/file_cache.rb +29 -0
  12. data/lib/rets/metadata/json_serializer.rb +27 -0
  13. data/lib/rets/metadata/lookup_table.rb +65 -0
  14. data/lib/rets/metadata/lookup_type.rb +3 -4
  15. data/lib/rets/metadata/marshal_serializer.rb +27 -0
  16. data/lib/rets/metadata/multi_lookup_table.rb +70 -0
  17. data/lib/rets/metadata/null_cache.rb +24 -0
  18. data/lib/rets/metadata/resource.rb +39 -29
  19. data/lib/rets/metadata/rets_class.rb +27 -23
  20. data/lib/rets/metadata/rets_object.rb +32 -0
  21. data/lib/rets/metadata/table.rb +9 -101
  22. data/lib/rets/metadata/table_factory.rb +19 -0
  23. data/lib/rets/metadata/yaml_serializer.rb +27 -0
  24. data/lib/rets/parser/compact.rb +61 -18
  25. data/lib/rets/parser/error_checker.rb +8 -1
  26. data/test/fixtures.rb +58 -0
  27. data/test/test_caching.rb +89 -0
  28. data/test/test_client.rb +44 -24
  29. data/test/test_error_checker.rb +18 -0
  30. data/test/test_file_cache.rb +42 -0
  31. data/test/test_http_client.rb +96 -60
  32. data/test/test_json_serializer.rb +26 -0
  33. data/test/test_marshal_serializer.rb +26 -0
  34. data/test/test_metadata.rb +62 -450
  35. data/test/test_metadata_class.rb +50 -0
  36. data/test/test_metadata_lookup_table.rb +21 -0
  37. data/test/test_metadata_lookup_type.rb +12 -0
  38. data/test/test_metadata_multi_lookup_table.rb +60 -0
  39. data/test/test_metadata_object.rb +20 -0
  40. data/test/test_metadata_resource.rb +140 -0
  41. data/test/test_metadata_root.rb +151 -0
  42. data/test/test_metadata_table.rb +21 -0
  43. data/test/test_metadata_table_factory.rb +24 -0
  44. data/test/test_parser_compact.rb +23 -28
  45. data/test/test_yaml_serializer.rb +26 -0
  46. metadata +29 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a9bf5e37008d7651782e347a520f24cb291931f
4
- data.tar.gz: 22742e9f4a45db910f03358e45024ee25d8cb6e9
3
+ metadata.gz: cedf0e419db8f2c813a4c2c97fd210909b9b58af
4
+ data.tar.gz: b8eb9ad36844f79503c64f020830ef477586fa20
5
5
  SHA512:
6
- metadata.gz: a742d37803a7c1354bb4f8d2c087b9a20b745c984286df5c243022cbb053612cb281350f589ca259494e955b7404bfd37db18ff2812441b2a560ad33485976f0
7
- data.tar.gz: 250667d8536d504b41fc4192d8bb124237cd48bd663d061baccca0728b6408abd2d8776cd3ccf70ba6d7458a9a56e61f83a853510be725b264940be0e0c777b9
6
+ metadata.gz: e116e7a292cb5e8118cfff090aae8147a431299fd79b81572bf0afb6d093c0152f1ad35c671e2199898ddaf5b8ea12061a011184776179a239b44c8a8baf9b74
7
+ data.tar.gz: d8f6da1337852f4ba871eabb531e31765e5a1a1167f57a9aad5b02ce01c6a242d260d7f6e63492e8fd64c89f9cb4da12a1ec344c20b39f519195cf075cdce666
@@ -1,3 +1,22 @@
1
+ ### 0.10.0 / 2016-02-29
2
+
3
+ * fix: ensure cookie store exists #133
4
+ * feature: make cached capabilities case insensitive #136
5
+ * feature: add specific classes for each rets error #137
6
+ * feature: whitelist RETS search options #142
7
+ * feature: simplify metadata caching #134
8
+ * feature: use a SAX parser #98
9
+ * fix: save capabilities to avoid double logins #148
10
+ * feature: login on authorization error #155
11
+ * add basic support for DataDictionary feeds #156
12
+ * fix: count always returns a number #161
13
+ * feature: make lookup tables case insensitive #163
14
+ * feature: update to httpclient 2.7 #165
15
+ * fix: getObject now works with non-multipart responses #166
16
+ * fix: getObject works with multiple ids #167
17
+ * feature: store rets object metadata #168
18
+ * feature: add a code of conduct #171
19
+
1
20
  ### 0.9.0 / 2015-06-11
2
21
 
3
22
  * feature: update to httpclient 2.6
@@ -13,22 +13,46 @@ lib/rets/http_client.rb
13
13
  lib/rets/locking_http_client.rb
14
14
  lib/rets/measuring_http_client.rb
15
15
  lib/rets/metadata.rb
16
+ lib/rets/metadata/caching.rb
16
17
  lib/rets/metadata/containers.rb
18
+ lib/rets/metadata/file_cache.rb
19
+ lib/rets/metadata/json_serializer.rb
20
+ lib/rets/metadata/lookup_table.rb
17
21
  lib/rets/metadata/lookup_type.rb
22
+ lib/rets/metadata/marshal_serializer.rb
23
+ lib/rets/metadata/multi_lookup_table.rb
24
+ lib/rets/metadata/null_cache.rb
18
25
  lib/rets/metadata/resource.rb
19
26
  lib/rets/metadata/rets_class.rb
27
+ lib/rets/metadata/rets_object.rb
20
28
  lib/rets/metadata/root.rb
21
29
  lib/rets/metadata/table.rb
30
+ lib/rets/metadata/table_factory.rb
31
+ lib/rets/metadata/yaml_serializer.rb
22
32
  lib/rets/parser/compact.rb
23
33
  lib/rets/parser/error_checker.rb
24
34
  lib/rets/parser/multipart.rb
25
35
  test/fixtures.rb
26
36
  test/helper.rb
37
+ test/test_caching.rb
27
38
  test/test_client.rb
28
39
  test/test_error_checker.rb
40
+ test/test_file_cache.rb
29
41
  test/test_http_client.rb
42
+ test/test_json_serializer.rb
30
43
  test/test_locking_http_client.rb
44
+ test/test_marshal_serializer.rb
31
45
  test/test_metadata.rb
46
+ test/test_metadata_class.rb
47
+ test/test_metadata_lookup_table.rb
48
+ test/test_metadata_lookup_type.rb
49
+ test/test_metadata_multi_lookup_table.rb
50
+ test/test_metadata_object.rb
51
+ test/test_metadata_resource.rb
52
+ test/test_metadata_root.rb
53
+ test/test_metadata_table.rb
54
+ test/test_metadata_table_factory.rb
32
55
  test/test_parser_compact.rb
33
56
  test/test_parser_multipart.rb
57
+ test/test_yaml_serializer.rb
34
58
  test/vcr_cassettes/unauthorized_response.yml
data/README.md CHANGED
@@ -28,7 +28,79 @@ gem 'rets'
28
28
  ## EXAMPLE USAGE:
29
29
 
30
30
  We need work in this area! There are currently a few guideline examples in the `example` folder on connecting, fetching a property's data, and fetching a property's photos.
31
-
31
+
32
+ ## Metadata caching
33
+
34
+ Metadata, which is loaded when a client is first started, can be slow
35
+ to fetch. To avoid the cost of fetching metadata every time the
36
+ client is started, metadata can be cached.
37
+
38
+ To cache metadata, pass the :metadata_cache option to the client when
39
+ you start it. The library comes with a predefined metadata cache that
40
+ persists the metadata to a file. It is created with the path to which
41
+ the cached metadata should be written:
42
+
43
+ metadata_cache = Rets::Metadata::FileCache.new("/tmp/metadata")
44
+
45
+ When you create the RETS client, pass it the metadata cache:
46
+
47
+ client = Rets::Client.new(
48
+ ...
49
+ metadata_cache: metadata_cache
50
+ )
51
+
52
+ If you want to persist to something other than a file, create your own
53
+ metadata cache object and pass it in. It should have the same interface
54
+ as the built-in Metadata::FileCache class:
55
+
56
+ class MyMetadataCache
57
+
58
+ # Save the metadata. Should yield an IO-like object to a block;
59
+ # that block will serialize the metadata to that object.
60
+ def save(&block)
61
+ end
62
+
63
+ # Load the metadata. Should yield an IO-like object to a block;
64
+ # that block will deserialize the metadata from that object and
65
+ # return the metadata. Returns the metadata, or nil if it could
66
+ # not be loaded.
67
+ def load(&block)
68
+ end
69
+
70
+ end
71
+
72
+ By default, the metadata is serialized using Marshal. You may select
73
+ JSON or YAML instead, or define your own serialization mechanism, using the
74
+ :metadata_serializer option when you create the Rets::Client:
75
+
76
+ client = Rets::Client.new(
77
+ ...
78
+ metadata_serializer: Rets::Metadata::JsonSerializer.new
79
+ )
80
+
81
+ The built-in serializers are:
82
+
83
+ * Rets::Metadata::JsonSerializer
84
+ * Rets::Metadata::MarshalSerializer
85
+ * Rets::Metadata::YamlSerializer
86
+
87
+ To define your own serializer, create an object with this interface:
88
+
89
+ class MySerializer
90
+
91
+ # Serialize to a file. The library reserves the right to change
92
+ # the type or contents of o, so don't depend on it being
93
+ # anything in particular.
94
+ def save(file, o)
95
+ end
96
+
97
+ # Deserialize from a file. If the metadata cannot be
98
+ # deserialized, return nil.
99
+ def load(file)
100
+ end
101
+
102
+ end
103
+
32
104
  ## LICENSE:
33
105
 
34
106
  (The MIT License)
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ Hoe.plugin :gemspec
9
9
  Hoe.spec 'rets' do
10
10
  developer 'Estately, Inc. Open Source', 'opensource@estately.com'
11
11
 
12
- extra_deps << [ "httpclient", "~> 2.6.0" ]
12
+ extra_deps << [ "httpclient", "~> 2.7.0" ]
13
13
  extra_deps << [ "http-cookie", "~> 1.0.0" ]
14
14
  extra_deps << [ "nokogiri", "~> 1.5" ]
15
15
 
@@ -3,8 +3,9 @@ require 'digest/md5'
3
3
  require 'nokogiri'
4
4
 
5
5
  module Rets
6
- VERSION = '0.9.0'
6
+ VERSION = '0.10.0'
7
7
 
8
+ HttpError = Class.new(StandardError)
8
9
  MalformedResponse = Class.new(ArgumentError)
9
10
  UnknownResponse = Class.new(ArgumentError)
10
11
  NoLogout = Class.new(ArgumentError)
@@ -55,6 +56,206 @@ module Rets
55
56
  super("unknown capability #{capability_name}, available capabilities #{capabilities}")
56
57
  end
57
58
  end
59
+
60
+ class SystemError < InvalidRequest
61
+ ERROR_CODE = 10000
62
+ end
63
+
64
+ class ZeroBalance < InvalidRequest
65
+ ERROR_CODE = 20003
66
+ end
67
+
68
+ class BrokerCodeRequired < InvalidRequest
69
+ ERROR_CODE = 20012
70
+ end
71
+
72
+ class AdditionalLoginNotPermitted < InvalidRequest
73
+ ERROR_CODE = 20022
74
+ end
75
+
76
+ class ServerLoginError < InvalidRequest
77
+ ERROR_CODE = 20036
78
+ end
79
+
80
+ class ClientAuthenticationFailed < InvalidRequest
81
+ ERROR_CODE = 20037
82
+ end
83
+
84
+ class UserAgentAuthenticationRequired < InvalidRequest
85
+ ERROR_CODE = 20041
86
+ end
87
+
88
+ class ServerTemporarilyDisabled < InvalidRequest
89
+ ERROR_CODE = 20050
90
+ end
91
+
92
+ class InsecurePassword < InvalidRequest
93
+ ERROR_CODE = 20140
94
+ end
95
+
96
+ class SameAsPreviousPassword < InvalidRequest
97
+ ERROR_CODE = 20141
98
+ end
99
+
100
+ class InvalidUserName < InvalidRequest
101
+ ERROR_CODE = 20142
102
+ end
103
+
104
+ class UnknownQueryField < InvalidRequest
105
+ ERROR_CODE = 20200
106
+ end
107
+
108
+ class InvalidSelect < InvalidRequest
109
+ ERROR_CODE = 20202
110
+ end
111
+
112
+ class MiscellaneousSearchError < InvalidRequest
113
+ ERROR_CODE = 20203
114
+ end
115
+
116
+ class InvalidQuerySyntax < InvalidRequest
117
+ ERROR_CODE = 20206
118
+ end
119
+
120
+ class UnauthorizedQuery < InvalidRequest
121
+ ERROR_CODE = 20207
122
+ end
123
+
124
+ class MaximumRecordsExceeded < InvalidRequest
125
+ ERROR_CODE = 20208
126
+ end
127
+
128
+ class RequestTimeout < InvalidRequest
129
+ ERROR_CODE = 20209
130
+ end
131
+
132
+ class TooManyOutstandingQueries < InvalidRequest
133
+ ERROR_CODE = 20210
134
+ end
135
+
136
+ class QueryTooComplex < InvalidRequest
137
+ ERROR_CODE = 20211
138
+ end
139
+
140
+ class InvalidKeyRequest < InvalidRequest
141
+ ERROR_CODE = 20212
142
+ end
143
+
144
+ class InvalidKey < InvalidRequest
145
+ ERROR_CODE = 20213
146
+ end
147
+
148
+ class InvalidParameter < InvalidRequest
149
+ ERROR_CODE = 20301
150
+ end
151
+
152
+ class UnableToSaveRecord < InvalidRequest
153
+ ERROR_CODE = 20301
154
+ end
155
+
156
+ class MiscellaneousUpdateError < InvalidRequest
157
+ ERROR_CODE = 20301
158
+ end
159
+
160
+ class WarningResponseNotGivenForAllWarnings < InvalidRequest
161
+ ERROR_CODE = 20311
162
+ end
163
+
164
+ class WarningResponseGivenForWarningNotRequired < InvalidRequest
165
+ ERROR_CODE = 20312
166
+ end
167
+
168
+ class InvalidResource < InvalidRequest
169
+ ERROR_CODE = 20400
170
+ end
171
+
172
+ class InvalidType < InvalidRequest
173
+ ERROR_CODE = 20401
174
+ end
175
+
176
+ class InvalidIdentifier < InvalidRequest
177
+ ERROR_CODE = 20402
178
+ end
179
+
180
+ class UnsupportedMimeType < InvalidRequest
181
+ ERROR_CODE = 20406
182
+ end
183
+
184
+ class UnauthorizedRetrieval < InvalidRequest
185
+ ERROR_CODE = 20407
186
+ end
187
+
188
+ class ResourceUnavailable < InvalidRequest
189
+ ERROR_CODE = 20408
190
+ end
191
+
192
+ class ObjectUnavailable < InvalidRequest
193
+ ERROR_CODE = 20409
194
+ end
195
+
196
+ class RequestTooLarge < InvalidRequest
197
+ ERROR_CODE = 20410
198
+ end
199
+
200
+ class ExecutionTimeout < InvalidRequest
201
+ ERROR_CODE = 20411
202
+ end
203
+
204
+ class TooManyOutstandingRequests < InvalidRequest
205
+ ERROR_CODE = 20412
206
+ end
207
+
208
+ class InvalidResourceRequested < InvalidRequest
209
+ ERROR_CODE = 20500
210
+ end
211
+
212
+ class InvalidMetadataType < InvalidRequest
213
+ ERROR_CODE = 20501
214
+ end
215
+
216
+ class InvalidIdentifierRequested < InvalidRequest
217
+ ERROR_CODE = 20502
218
+ end
219
+
220
+ class NoMetadataFound < InvalidRequest
221
+ ERROR_CODE = 20503
222
+ end
223
+
224
+ class UnsupportedMetadataMimeType < InvalidRequest
225
+ ERROR_CODE = 20506
226
+ end
227
+
228
+ class UnauthorizedMetadataRetrieval < InvalidRequest
229
+ ERROR_CODE = 20507
230
+ end
231
+
232
+ class MetadataResourceUnavailable < InvalidRequest
233
+ ERROR_CODE = 20508
234
+ end
235
+
236
+ class MetadataUnavailable < InvalidRequest
237
+ ERROR_CODE = 20509
238
+ end
239
+
240
+ class MetadataRequestTooLarge < InvalidRequest
241
+ ERROR_CODE = 20510
242
+ end
243
+
244
+ class MetadataRequestTimeout < InvalidRequest
245
+ ERROR_CODE = 20511
246
+ end
247
+
248
+ class TooManyOutstandingMetadataRequests < InvalidRequest
249
+ ERROR_CODE = 20512
250
+ end
251
+
252
+ class RequestedDTDVersionUnavailable < InvalidRequest
253
+ ERROR_CODE = 20514
254
+ end
255
+
256
+ class NotLoggedIn < InvalidRequest
257
+ ERROR_CODE = 20701
258
+ end
58
259
  end
59
260
 
60
261
  require 'rets/http_client'
@@ -1,17 +1,11 @@
1
- require 'http-cookie'
2
- require 'httpclient'
3
1
  require 'logger'
4
2
 
5
3
  module Rets
6
- class HttpError < StandardError ; end
7
-
8
4
  class Client
9
- DEFAULT_OPTIONS = {}
10
-
11
5
  COUNT = Struct.new(:exclude, :include, :only).new(0,1,2)
6
+ CASE_INSENSITIVE_PROC = Proc.new { |h,k| h.key?(k.downcase) ? h[k.downcase] : nil }
12
7
 
13
- attr_accessor :login_url, :options, :logger
14
- attr_writer :capabilities, :metadata
8
+ attr_accessor :cached_metadata, :client_progress, :logger, :login_url, :options
15
9
 
16
10
  def initialize(options)
17
11
  @options = options
@@ -19,41 +13,15 @@ module Rets
19
13
  end
20
14
 
21
15
  def clean_setup
22
- self.options = DEFAULT_OPTIONS.merge(@options)
23
- self.login_url = self.options[:login_url]
24
-
25
- @cached_metadata = nil
26
- @capabilities = nil
27
- @metadata = nil
28
- @tries = nil
29
- self.capabilities = nil
30
-
31
- self.logger = @options[:logger] || FakeLogger.new
32
- @client_progress = ClientProgressReporter.new(self.logger, options[:stats_collector], options[:stats_prefix])
33
- @cached_metadata = @options[:metadata]
34
- if @options[:http_proxy]
35
- @http = HTTPClient.new(options.fetch(:http_proxy))
36
-
37
- if @options[:proxy_username]
38
- @http.set_proxy_auth(options.fetch(:proxy_username), options.fetch(:proxy_password))
39
- end
40
- else
41
- @http = HTTPClient.new
42
- end
43
-
44
- if @options[:receive_timeout]
45
- @http.receive_timeout = @options[:receive_timeout]
46
- end
47
-
48
- @http.set_cookie_store(options[:cookie_store]) if options[:cookie_store]
49
-
50
- @http_client = Rets::HttpClient.new(@http, @options, @logger, @login_url)
51
- if options[:http_timing_stats_collector]
52
- @http_client = Rets::MeasuringHttpClient.new(@http_client, options.fetch(:http_timing_stats_collector), options.fetch(:http_timing_stats_prefix))
53
- end
54
- if options[:lock_around_http_requests]
55
- @http_client = Rets::LockingHttpClient.new(@http_client, options.fetch(:locker), options.fetch(:lock_name), options.fetch(:lock_options))
56
- end
16
+ @metadata = nil
17
+ @tries = nil
18
+ @login_url = options[:login_url]
19
+ @cached_metadata = options[:metadata]
20
+ @cached_capabilities = options[:capabilities]
21
+ @logger = options[:logger] || FakeLogger.new
22
+ @client_progress = ClientProgressReporter.new(logger, options[:stats_collector], options[:stats_prefix])
23
+ @http_client = Rets::HttpClient.from_options(options, logger)
24
+ @caching = Metadata::Caching.make(options)
57
25
  end
58
26
 
59
27
  # Attempts to login by making an empty request to the URL provided in
@@ -63,9 +31,11 @@ module Rets
63
31
  res = http_get(login_url)
64
32
  Parser::ErrorChecker.check(res)
65
33
 
66
- self.capabilities = extract_capabilities(Nokogiri.parse(res.body))
67
- raise UnknownResponse, "Cannot read rets server capabilities." unless @capabilities
68
- @capabilities
34
+ new_capabilities = extract_capabilities(Nokogiri.parse(res.body))
35
+ unless new_capabilities
36
+ raise UnknownResponse, "Cannot read rets server capabilities."
37
+ end
38
+ @capabilities = new_capabilities
69
39
  end
70
40
 
71
41
  def logout
@@ -110,39 +80,58 @@ module Rets
110
80
 
111
81
  def find_with_retries(opts = {})
112
82
  retries = 0
113
- resolve = opts.delete(:resolve)
114
- find_with_given_retry(retries, resolve, opts)
83
+ find_with_given_retry(retries, opts)
115
84
  end
116
85
 
117
- def find_with_given_retry(retries, resolve, opts)
86
+ def find_with_given_retry(retries, opts)
118
87
  begin
119
- find_every(opts, resolve)
88
+ find_every(opts)
120
89
  rescue NoRecordsFound => e
121
90
  if opts.fetch(:no_records_not_an_error, false)
122
- @client_progress.no_records_found
91
+ client_progress.no_records_found
123
92
  opts[:count] == COUNT.only ? 0 : []
124
93
  else
125
- handle_find_failure(retries, resolve, opts, e)
94
+ handle_find_failure(retries, opts, e)
126
95
  end
127
- rescue AuthorizationFailure, InvalidRequest => e
128
- handle_find_failure(retries, resolve, opts, e)
96
+ rescue InvalidRequest, HttpError => e
97
+ handle_find_failure(retries, opts, e)
98
+ rescue AuthorizationFailure => e
99
+ login
100
+ handle_find_failure(retries, opts, e)
129
101
  end
130
102
  end
131
103
 
132
- def handle_find_failure(retries, resolve, opts, e)
104
+ def handle_find_failure(retries, opts, e)
133
105
  if retries < opts.fetch(:max_retries, 3)
134
106
  retries += 1
135
- @client_progress.find_with_retries_failed_a_retry(e, retries)
107
+ client_progress.find_with_retries_failed_a_retry(e, retries)
136
108
  clean_setup
137
- find_with_given_retry(retries, resolve, opts)
109
+ find_with_given_retry(retries, opts)
138
110
  else
139
- @client_progress.find_with_retries_exceeded_retry_count(e)
111
+ client_progress.find_with_retries_exceeded_retry_count(e)
140
112
  raise e
141
113
  end
142
114
  end
143
115
 
144
- def find_every(opts, resolve)
145
- params = {"QueryType" => "DMQL2", "Format" => "COMPACT"}.merge(fixup_keys(opts))
116
+ def find_every(opts)
117
+ raise ArgumentError.new("missing option :search_type (provide the name of a RETS resource)") unless opts[:search_type]
118
+ raise ArgumentError.new("missing option :class (provide the name of a RETS class)") unless opts[:class]
119
+
120
+ params = {
121
+ "SearchType" => opts.fetch(:search_type),
122
+ "Class" => opts.fetch(:class),
123
+
124
+ "Count" => opts[:count],
125
+ "Format" => opts.fetch(:format, "COMPACT"),
126
+ "Limit" => opts[:limit],
127
+ "Offset" => opts[:offset],
128
+ "Select" => opts[:select],
129
+ "RestrictedIndicator" => opts[:RestrictedIndicator],
130
+ "StandardNames" => opts[:standard_name],
131
+ "Payload" => opts[:payload],
132
+ "Query" => opts[:query],
133
+ "QueryType" => opts.fetch(:query_type, "DMQL2"),
134
+ }.reject { |k,v| v.nil? }
146
135
  res = http_post(capability_url("Search"), params)
147
136
 
148
137
  if opts[:count] == COUNT.only
@@ -151,7 +140,7 @@ module Rets
151
140
  results = Parser::Compact.parse_document(
152
141
  res.body.encode("UTF-8", res.body.encoding, :invalid => :replace, :undef => :replace)
153
142
  )
154
- if resolve
143
+ if opts[:resolve]
155
144
  rets_class = find_rets_class(opts[:search_type], opts[:class])
156
145
  decorate_results(results, rets_class)
157
146
  else
@@ -177,7 +166,7 @@ module Rets
177
166
  result[key] = table.resolve(value.to_s)
178
167
  else
179
168
  #can't resolve just leave the value be
180
- @client_progress.could_not_resolve_find_metadata(key)
169
+ client_progress.could_not_resolve_find_metadata(key)
181
170
  end
182
171
  end
183
172
  end
@@ -191,7 +180,7 @@ module Rets
191
180
  def objects(object_ids, opts = {})
192
181
  response = case object_ids
193
182
  when String then fetch_object(object_ids, opts)
194
- when Array then fetch_object(object_ids.join(","), opts)
183
+ when Array then fetch_object(object_ids.join(":"), opts)
195
184
  else raise ArgumentError, "Expected instance of String or Array, but got #{object_ids.inspect}."
196
185
  end
197
186
 
@@ -214,10 +203,11 @@ module Rets
214
203
 
215
204
  return parts
216
205
  else
206
+ logger.debug "Rets::Client: Found 1 part (the whole body)"
207
+
217
208
  # fake a multipart for interface compatibility
218
209
  headers = {}
219
- response.headers.each { |k,v| headers[k] = v[0] }
220
-
210
+ response.headers.each { |k,v| headers[k.downcase] = v }
221
211
  part = Parser::Multipart::Part.new(headers, response.body)
222
212
 
223
213
  return [part]
@@ -250,35 +240,24 @@ module Rets
250
240
  http_post(capability_url("GetObject"), params, extra_headers)
251
241
  end
252
242
 
253
- # Changes keys to be camel cased, per the RETS standard for queries.
254
- def fixup_keys(hash)
255
- fixed_hash = {}
256
-
257
- hash.each do |key, value|
258
- camel_cased_key = key.to_s.capitalize.gsub(/_(\w)/) { $1.upcase }
259
-
260
- fixed_hash[camel_cased_key] = value
261
- end
262
-
263
- fixed_hash
264
- end
265
-
266
- def metadata
243
+ def metadata(types=nil)
267
244
  return @metadata if @metadata
268
-
269
- if @cached_metadata && (@options[:skip_metadata_uptodate_check] ||
270
- @cached_metadata.current?(capabilities["MetadataTimestamp"], capabilities["MetadataVersion"]))
271
- @client_progress.use_cached_metadata
272
- self.metadata = @cached_metadata
245
+ @cached_metadata ||= @caching.load(@logger)
246
+ if cached_metadata && (options[:skip_metadata_uptodate_check] ||
247
+ cached_metadata.current?(capabilities["MetadataTimestamp"], capabilities["MetadataVersion"]))
248
+ client_progress.use_cached_metadata
249
+ @metadata = cached_metadata
273
250
  else
274
- @client_progress.bad_cached_metadata(@cached_metadata)
275
- self.metadata = Metadata::Root.new(logger, retrieve_metadata)
251
+ client_progress.bad_cached_metadata(cached_metadata)
252
+ @metadata = Metadata::Root.new(logger, retrieve_metadata(types))
253
+ @caching.save(metadata)
276
254
  end
255
+ @metadata
277
256
  end
278
257
 
279
- def retrieve_metadata
258
+ def retrieve_metadata(types=nil)
280
259
  raw_metadata = {}
281
- Metadata::METADATA_TYPES.each {|type|
260
+ (types || Metadata::METADATA_TYPES).each {|type|
282
261
  raw_metadata[type] = retrieve_metadata_type(type)
283
262
  }
284
263
  raw_metadata
@@ -301,7 +280,13 @@ module Rets
301
280
  #
302
281
  # [1] In fact, sometimes only a path is returned from the server.
303
282
  def capabilities
304
- @capabilities || login
283
+ if @capabilities
284
+ @capabilities
285
+ elsif @cached_capabilities
286
+ @capabilities = add_case_insensitive_default_proc(@cached_capabilities)
287
+ else
288
+ login
289
+ end
305
290
  end
306
291
 
307
292
  def capability_url(name)
@@ -325,15 +310,19 @@ module Rets
325
310
  def extract_capabilities(document)
326
311
  raw_key_values = document.xpath("/RETS/RETS-RESPONSE").text.strip
327
312
 
328
- hash = Hash.new{|h,k| h.key?(k.downcase) ? h[k.downcase] : nil }
329
-
330
313
  # ... :(
331
314
  # Feel free to make this better. It has a test.
332
- raw_key_values.split(/\n/).
315
+ hash = raw_key_values.split(/\n/).
333
316
  map { |r| r.split(/\=/, 2) }.
334
- each { |k,v| hash[k.strip.downcase] = v.strip }
317
+ each_with_object({}) { |(k,v), h| h[k.strip.downcase] = v.strip }
318
+
319
+ add_case_insensitive_default_proc(hash)
320
+ end
335
321
 
336
- hash
322
+ def add_case_insensitive_default_proc(hash)
323
+ new_hash = hash.dup
324
+ new_hash.default_proc = CASE_INSENSITIVE_PROC
325
+ new_hash
337
326
  end
338
327
 
339
328
  def save_cookie_store