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.
- 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
|