rets 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/Manifest.txt +24 -0
- data/README.md +73 -1
- data/Rakefile +1 -1
- data/lib/rets.rb +202 -1
- data/lib/rets/client.rb +83 -94
- data/lib/rets/http_client.rb +42 -0
- data/lib/rets/metadata.rb +15 -3
- data/lib/rets/metadata/caching.rb +59 -0
- data/lib/rets/metadata/file_cache.rb +29 -0
- data/lib/rets/metadata/json_serializer.rb +27 -0
- data/lib/rets/metadata/lookup_table.rb +65 -0
- data/lib/rets/metadata/lookup_type.rb +3 -4
- data/lib/rets/metadata/marshal_serializer.rb +27 -0
- data/lib/rets/metadata/multi_lookup_table.rb +70 -0
- data/lib/rets/metadata/null_cache.rb +24 -0
- data/lib/rets/metadata/resource.rb +39 -29
- data/lib/rets/metadata/rets_class.rb +27 -23
- data/lib/rets/metadata/rets_object.rb +32 -0
- data/lib/rets/metadata/table.rb +9 -101
- data/lib/rets/metadata/table_factory.rb +19 -0
- data/lib/rets/metadata/yaml_serializer.rb +27 -0
- data/lib/rets/parser/compact.rb +61 -18
- data/lib/rets/parser/error_checker.rb +8 -1
- data/test/fixtures.rb +58 -0
- data/test/test_caching.rb +89 -0
- data/test/test_client.rb +44 -24
- data/test/test_error_checker.rb +18 -0
- data/test/test_file_cache.rb +42 -0
- data/test/test_http_client.rb +96 -60
- data/test/test_json_serializer.rb +26 -0
- data/test/test_marshal_serializer.rb +26 -0
- data/test/test_metadata.rb +62 -450
- data/test/test_metadata_class.rb +50 -0
- data/test/test_metadata_lookup_table.rb +21 -0
- data/test/test_metadata_lookup_type.rb +12 -0
- data/test/test_metadata_multi_lookup_table.rb +60 -0
- data/test/test_metadata_object.rb +20 -0
- data/test/test_metadata_resource.rb +140 -0
- data/test/test_metadata_root.rb +151 -0
- data/test/test_metadata_table.rb +21 -0
- data/test/test_metadata_table_factory.rb +24 -0
- data/test/test_parser_compact.rb +23 -28
- data/test/test_yaml_serializer.rb +26 -0
- metadata +29 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cedf0e419db8f2c813a4c2c97fd210909b9b58af
|
4
|
+
data.tar.gz: b8eb9ad36844f79503c64f020830ef477586fa20
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e116e7a292cb5e8118cfff090aae8147a431299fd79b81572bf0afb6d093c0152f1ad35c671e2199898ddaf5b8ea12061a011184776179a239b44c8a8baf9b74
|
7
|
+
data.tar.gz: d8f6da1337852f4ba871eabb531e31765e5a1a1167f57a9aad5b02ce01c6a242d260d7f6e63492e8fd64c89f9cb4da12a1ec344c20b39f519195cf075cdce666
|
data/CHANGELOG.md
CHANGED
@@ -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
|
data/Manifest.txt
CHANGED
@@ -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.
|
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
|
|
data/lib/rets.rb
CHANGED
@@ -3,8 +3,9 @@ require 'digest/md5'
|
|
3
3
|
require 'nokogiri'
|
4
4
|
|
5
5
|
module Rets
|
6
|
-
VERSION = '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'
|
data/lib/rets/client.rb
CHANGED
@@ -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 :
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
@cached_metadata
|
26
|
-
@
|
27
|
-
@
|
28
|
-
@
|
29
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
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,
|
86
|
+
def find_with_given_retry(retries, opts)
|
118
87
|
begin
|
119
|
-
find_every(opts
|
88
|
+
find_every(opts)
|
120
89
|
rescue NoRecordsFound => e
|
121
90
|
if opts.fetch(:no_records_not_an_error, false)
|
122
|
-
|
91
|
+
client_progress.no_records_found
|
123
92
|
opts[:count] == COUNT.only ? 0 : []
|
124
93
|
else
|
125
|
-
handle_find_failure(retries,
|
94
|
+
handle_find_failure(retries, opts, e)
|
126
95
|
end
|
127
|
-
rescue
|
128
|
-
handle_find_failure(retries,
|
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,
|
104
|
+
def handle_find_failure(retries, opts, e)
|
133
105
|
if retries < opts.fetch(:max_retries, 3)
|
134
106
|
retries += 1
|
135
|
-
|
107
|
+
client_progress.find_with_retries_failed_a_retry(e, retries)
|
136
108
|
clean_setup
|
137
|
-
find_with_given_retry(retries,
|
109
|
+
find_with_given_retry(retries, opts)
|
138
110
|
else
|
139
|
-
|
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
|
145
|
-
|
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
|
-
|
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("
|
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
|
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
|
-
|
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
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
275
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|