wku-ruby-rets 2.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +104 -0
- data/MIT-LICENSE +19 -0
- data/README.md +43 -0
- data/Rakefile +12 -0
- data/lib/rets/base/core.rb +303 -0
- data/lib/rets/base/sax_metadata.rb +63 -0
- data/lib/rets/base/sax_search.rb +58 -0
- data/lib/rets/client.rb +75 -0
- data/lib/rets/exceptions.rb +37 -0
- data/lib/rets/http.rb +346 -0
- data/lib/rets/stream_http.rb +170 -0
- data/lib/rets/version.rb +3 -0
- data/lib/ruby-rets.rb +9 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7334380eaedc3dd2482334b030f6c3f9c561351f
|
4
|
+
data.tar.gz: 2d959fe4c4c2077cd73f4593ac89131d81fa45a4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 802e0d0fe5d4b17305bea448083ea16fde66ed90ab4d92a3381ae30b1a3dbb22afe9484bc5fdf6bd01c0bce9834505b70292fd56722a4dc769c787890f5b47ed
|
7
|
+
data.tar.gz: ea43b713311a72b218819f1ea93ffa89af2ebb5eabd5c224f46143e232e84be310878b1e10272269b94af2f93893bbfe9ce57b893e55db954bb24ed8e8ec63d1
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# Overview
|
2
|
+
|
3
|
+
## 2.0.8 [In Progress]
|
4
|
+
|
5
|
+
### Features
|
6
|
+
* Added support for using a HTTP Proxy
|
7
|
+
* Streaming can be disabled for `get_metadata` calls
|
8
|
+
|
9
|
+
### Fixes
|
10
|
+
* Disabled gzip while streaming is enabled to fix parsing issues, it's still enabled when streaming is disabled (`:disable_stream => true`)
|
11
|
+
* No longer errors if Set-Cookie is returned with an invalid value (RAPRETS)
|
12
|
+
|
13
|
+
## 2.0.7
|
14
|
+
|
15
|
+
Confirmed to work with Ruby 2.0.0-p0
|
16
|
+
|
17
|
+
### Fixes
|
18
|
+
* `RETS-Version` is no longer set to `RETS/1.7` unless an error `20037` is returned and no other one is specified
|
19
|
+
* `RETS-Version` can be set based on a HTTP 200 response for systems which only pass it after authentication (Innovia)
|
20
|
+
|
21
|
+
## 2.0.6
|
22
|
+
|
23
|
+
### Features
|
24
|
+
* System name added to `rets_data[:system_id]` when pulling metadata out
|
25
|
+
* `client.request_size` and `client.request_hash` can be used when streaming is disabled in `client.search`
|
26
|
+
* Added `client.request_time` which gives the time taken in seconds for a `client.search` or `client.get_object` to run
|
27
|
+
|
28
|
+
### Fixes
|
29
|
+
* Fixed an authentication issue when using Retsiq where it would return 20041 in place of HTTP 401
|
30
|
+
* Fixed errors when streaming with an unknown Content-Length and chunked encoding
|
31
|
+
* Fixed a block being required when making a `client.search` call with `:count_mode => :only`
|
32
|
+
* Fixed character encoding that was causing issues on some RETS servers (Paul Trippett)
|
33
|
+
* Fixed an exception when Rapattoni RETS servers returned a "RETS-STATUS" XML tag on a multipart `client.get_object` call
|
34
|
+
* Fixed a `RETS::APIError` code 0 error under some RETS systems when making a call to `client.get_object` with `:location`
|
35
|
+
|
36
|
+
## 2.0.5
|
37
|
+
|
38
|
+
### Features
|
39
|
+
* Make `client.rets_data` available immediately when calling `client.search` rather than having to wait until it finishes (Paul Trippett)
|
40
|
+
* `:rets_version` can be passed to `client.login` without the User-Agent fields being set, for RETS servers that require the version to be passed initially
|
41
|
+
|
42
|
+
### Fixes
|
43
|
+
* Default to HTTP Digest authentication when Digest and Basic are provided (Paul Trippett)
|
44
|
+
* Fixed authentication header parser if a server returns Basic/Digest and the Digest data goes stale (Paul Trippett)
|
45
|
+
* Fixed query strings from the initial login request not being saved when discovery service URLs (Paul Trippett)
|
46
|
+
|
47
|
+
## 2.0.4
|
48
|
+
|
49
|
+
### Features
|
50
|
+
* Added support for RETS servers that use digest authentication without the quality of protection flag (MRIS)
|
51
|
+
* Added SSL support (Paul Trippett)
|
52
|
+
|
53
|
+
### Fixes
|
54
|
+
* Fixed metadata parsing breaking if a field wasn't filled out (Paul Trippett)
|
55
|
+
* Fixed multipart parsing for `client.get_object` if a part is blank
|
56
|
+
|
57
|
+
## 2.0.3
|
58
|
+
|
59
|
+
### Fixes
|
60
|
+
* Fixed a stack overflow due to how Interealty handles User-Agent authentication errors
|
61
|
+
|
62
|
+
## 2.0.2
|
63
|
+
|
64
|
+
### Features
|
65
|
+
* Dropped support for TimeoutSeconds, instead if an HTTP 401 is received after a successful request then a reauthentication is forced. Provides better compatibility with how some RETS implementations handle sessions
|
66
|
+
|
67
|
+
### Fixes
|
68
|
+
* Client methods no longer return the HTTP request
|
69
|
+
* Requests will correctly be called after a HTTP digest becomes stale
|
70
|
+
|
71
|
+
## 2.0.1
|
72
|
+
|
73
|
+
### API Changes
|
74
|
+
* `client.login` will now raise `ResponseError` errors if the RETS tag cannot be found in the response
|
75
|
+
* `client.login` added the ability to pass `:rets_version` to force the RETS Version used in HTTP requests. Provides a small speedup as it can skip one HTTP request depending on the RETS implementation
|
76
|
+
* `client.get_object` can return both Content-Description or Description rather than just Description. Also will return Preferred
|
77
|
+
|
78
|
+
### Features
|
79
|
+
* Added support for TimeoutSeconds, after the timeout passes the gem seamlessly reauthenticates
|
80
|
+
* Improved the edge case handling for authentication requests to greatly increase compatability with logging into any RETS based system
|
81
|
+
|
82
|
+
### Fixes
|
83
|
+
* Object multipart parsing no longer fails if the boundary is wrapped in quotes
|
84
|
+
* Response parsing won't fail if the RETS server uses odd casing for the "ReplyText" and "ReplyCode" args in RETS
|
85
|
+
|
86
|
+
## 2.0.0
|
87
|
+
|
88
|
+
### API Changes
|
89
|
+
* `client.logout` will now raise `CapabilityNotFound` errors if it's unsupported
|
90
|
+
* `client.get_object` now requires a block which is yielded to rather than returning an array of the content
|
91
|
+
* `client.get_object` headers are now returned in lowercase form ("content-id" not "Content-ID" and so on)
|
92
|
+
* `RETS::Client.login` now uses `:useragent => {:name => "Foo", :password => "Bar"}` to pass User Agent data
|
93
|
+
* `RETS::Client.login` no longer implies the User-Agent username or password by the primary username and password
|
94
|
+
|
95
|
+
### Features
|
96
|
+
* Added support for Count, Offset, Select and RestrictedIndicators in `client.search`
|
97
|
+
* Added support for Location in `client.get_object`
|
98
|
+
* RETS reply code, text and other data such as count or delimiter can be gotten through `client.rets_data` after the call is finished
|
99
|
+
|
100
|
+
### Fixes
|
101
|
+
* Redid how authentication is handled, no longer implies HTTP Basic auth when using RETS-UA-Authorization
|
102
|
+
* RETS-Version is now used for RETS-UA-Authorization when available with "RETS/1.7" as a fallback
|
103
|
+
* Exceptions are now raised consistently and have been simplifed to `APIError`, `HTTPError`, `Unauthorized` and `CapabilityNotFound`
|
104
|
+
* `HTTPError` and `APIError` now include the reply text and code in `reply_code` and `reply_text`
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 Placester, Inc
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
Overview
|
2
|
+
===
|
3
|
+
Simplifies the process of dealing with RETS servers. Without having to worry about the various authentication methods or edge cases associated with dealing with RETS. Should work against all 1.x implementations.
|
4
|
+
|
5
|
+
Compability
|
6
|
+
-
|
7
|
+
Tested against Ruby 1.9.3, 2.0.0, RBX and JRuby, build history is available [here](http://travis-ci.org/Placester/ruby-rets).
|
8
|
+
|
9
|
+
<img src="https://secure.travis-ci.org/Placester/ruby-rets.png?branch=master&.png"/>
|
10
|
+
|
11
|
+
Documentation
|
12
|
+
-
|
13
|
+
See http://rubydoc.info/github/Placester/ruby-rets/master/frames for full documentation.
|
14
|
+
|
15
|
+
Examples
|
16
|
+
-
|
17
|
+
|
18
|
+
client = RETS::Client.login(:url => "http://foobar.com/rets/Login", :username => "foo", :password => "bar")
|
19
|
+
client.search(:search_type => :Property, :class => :RES, :query => "(ListPrice=50000-)") do |data|
|
20
|
+
# RETS data in key/value format, as COMPACT-DECODED
|
21
|
+
end
|
22
|
+
|
23
|
+
client.get_object(:resource => :Property, :type => :Photo, :location => false, :id => "1:0:*") do |headers, content|
|
24
|
+
puts "Object-ID #{headers"object-id"]}, Content-ID #{headers["content-id"]}, Description #{["description"]}"
|
25
|
+
puts "Data"
|
26
|
+
puts content
|
27
|
+
end
|
28
|
+
|
29
|
+
VCR / WebMock
|
30
|
+
-
|
31
|
+
Due to the streaming parser, the search features won't work with a library like VCR or Ephemeral Response. For WebMock, you can use the below patch to enable support for saving the HTTP requests to speed up your own tests.
|
32
|
+
|
33
|
+
module Net
|
34
|
+
module WebMockHTTPResponse
|
35
|
+
def self.extended(response)
|
36
|
+
response.instance_variable_set(:@socket, StringIO.new(response.body))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
License
|
42
|
+
-
|
43
|
+
Licensed under MIT
|
data/Rakefile
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
# For more information on what the possible values of fields that are passed to the RETS server can be, see {http://www.rets.org/documentation}.
|
2
|
+
module RETS
|
3
|
+
module Base
|
4
|
+
class Core
|
5
|
+
GET_OBJECT_DATA = ["object-id", "description", "content-id", "content-description", "location", "content-type", "preferred"]
|
6
|
+
|
7
|
+
# Can be called after any {RETS::Base::Core} call that hits the RETS Server.
|
8
|
+
# @return [String] How big the request was
|
9
|
+
attr_reader :request_size
|
10
|
+
|
11
|
+
# Can be called after any {RETS::Base::Core} call that hits the RETS Server.
|
12
|
+
# @return [String] SHA1 hash of the request
|
13
|
+
attr_reader :request_hash
|
14
|
+
|
15
|
+
# Can be called after any {#get_object} or {#search} call that hits the RETS Server.
|
16
|
+
# @return [Float] How long the request took
|
17
|
+
attr_reader :request_time
|
18
|
+
|
19
|
+
# Can be called after any {RETS::Base::Core} call that hits the RETS Server.
|
20
|
+
# @return [Hash]
|
21
|
+
# Gives access to the miscellaneous RETS data, such as reply text, code, delimiter, count and so on depending on the API call made.
|
22
|
+
# * *text* (String) - Reply text from the server
|
23
|
+
# * *code* (String) - Reply code from the server
|
24
|
+
attr_reader :rets_data
|
25
|
+
|
26
|
+
def initialize(http, urls)
|
27
|
+
@http = http
|
28
|
+
@urls = urls
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Attempts to logout of the RETS server.
|
33
|
+
#
|
34
|
+
# @raise [RETS::CapabilityNotFound]
|
35
|
+
# @raise [RETS::APIError]
|
36
|
+
# @raise [RETS::HTTPError]
|
37
|
+
def logout
|
38
|
+
unless @urls[:logout]
|
39
|
+
raise RETS::CapabilityNotFound.new("No Logout capability found for given user.")
|
40
|
+
end
|
41
|
+
|
42
|
+
@http.request(:url => @urls[:logout])
|
43
|
+
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Whether the RETS server has the requested capability.
|
49
|
+
#
|
50
|
+
# @param [Symbol] type Lowercase of the capability, "getmetadata", "getobject" and so on
|
51
|
+
# @return [Boolean]
|
52
|
+
def has_capability?(type)
|
53
|
+
@urls.has_key?(type)
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Requests metadata from the RETS server.
|
58
|
+
#
|
59
|
+
# @param [Hash] args
|
60
|
+
# @option args [String] :type Metadata to request, the same value if you were manually making the request, "METADATA-SYSTEM", "METADATA-CLASS" and so on
|
61
|
+
# @option args [String] :id Filter the data returned by ID, "*" would return all available data
|
62
|
+
# @option args [Integer, Optional] :read_timeout How many seconds to wait before giving up
|
63
|
+
# @option args [Boolean, Optional] :disable_stream Disables the streaming setup for data and instead loads it all and then parses
|
64
|
+
#
|
65
|
+
# @yield For every group of metadata downloaded
|
66
|
+
# @yieldparam [String] :type Type of data that was parsed with "METADATA-" stripped out, for "METADATA-SYSTEM" this will be "SYSTEM"
|
67
|
+
# @yieldparam [Hash] :attrs Attributes of the data, generally *Version*, *Date* and *Resource* but can vary depending on what metadata you requested
|
68
|
+
# @yieldparam [Array] :metadata Array of hashes with metadata info
|
69
|
+
#
|
70
|
+
# @raise [RETS::CapabilityNotFound]
|
71
|
+
# @raise [RETS::APIError]
|
72
|
+
# @raise [RETS::HTTPError]
|
73
|
+
# @see #rets_data
|
74
|
+
# @see #request_size
|
75
|
+
# @see #request_hash
|
76
|
+
def get_metadata(args, &block)
|
77
|
+
raise ArgumentError, "No block passed" unless block_given?
|
78
|
+
|
79
|
+
unless @urls[:getmetadata]
|
80
|
+
raise RETS::CapabilityNotFound.new("No GetMetadata capability found for given user.")
|
81
|
+
end
|
82
|
+
|
83
|
+
@request_size, @request_hash, @request_time, @rets_data = nil, nil, nil, nil
|
84
|
+
|
85
|
+
start = Time.now.utc.to_f
|
86
|
+
@http.request(:url => @urls[:getmetadata], :read_timeout => args[:read_timeout], :disable_compression => !args[:disable_stream], :params => {:Format => :COMPACT, :Type => args[:type], :ID => args[:id]}) do |response|
|
87
|
+
if args[:disable_stream]
|
88
|
+
stream = StringIO.new(response.body)
|
89
|
+
@request_time = Time.now.utc.to_f - start
|
90
|
+
else
|
91
|
+
stream = RETS::StreamHTTP.new(response)
|
92
|
+
end
|
93
|
+
|
94
|
+
sax = RETS::Base::SAXMetadata.new(block)
|
95
|
+
Nokogiri::XML::SAX::Parser.new(sax).parse_io(stream)
|
96
|
+
|
97
|
+
if args[:disable_stream]
|
98
|
+
@request_size, @request_hash = response.body.length, Digest::SHA1.hexdigest(response.body)
|
99
|
+
else
|
100
|
+
@request_size, @request_hash = stream.size, stream.hash
|
101
|
+
end
|
102
|
+
|
103
|
+
@rets_data = sax.rets_data
|
104
|
+
end
|
105
|
+
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Requests an object from the RETS server.
|
111
|
+
#
|
112
|
+
# @param [Hash] args
|
113
|
+
# @option args [String] :resource Resource to load, typically *Property*
|
114
|
+
# @option args [String] :type Type of object you want, usually *Photo*
|
115
|
+
# @option args [String] :id What objects to return
|
116
|
+
# @option args [Array, Optional] :accept Array of MIME types to accept, by default this is *image/png*, *image/gif* and *image/jpeg*
|
117
|
+
# @option args [Boolean, Optional] :location Return the location of the object rather than the contents of it
|
118
|
+
# @option args [Integer, Optional] :read_timeout How many seconds to wait before timing out
|
119
|
+
#
|
120
|
+
# @yield For every object downloaded
|
121
|
+
# @yieldparam [Hash] :headers Object headers
|
122
|
+
# * *object-id* (String) - Objects ID
|
123
|
+
# * *content-id* (String) - Content ID
|
124
|
+
# * *content-type* (String) - MIME type of the content
|
125
|
+
# * *description* (String, Optional) - A description of the object
|
126
|
+
# * *location* (String, Optional) - Where the file is located, only returned is *location* is true
|
127
|
+
# @yieldparam [String, Optional] :content Content for the object, not called when *location* is set
|
128
|
+
#
|
129
|
+
# @raise [RETS::CapabilityNotFound]
|
130
|
+
# @raise [RETS::APIError]
|
131
|
+
# @raise [RETS::HTTPError]
|
132
|
+
# @see #rets_data
|
133
|
+
# @see #request_size
|
134
|
+
# @see #request_hash
|
135
|
+
def get_object(args, &block)
|
136
|
+
raise ArgumentError, "No block passed" unless block_given?
|
137
|
+
|
138
|
+
unless @urls[:getobject]
|
139
|
+
raise RETS::CapabilityNotFound.new("No GetObject capability found for given user.")
|
140
|
+
end
|
141
|
+
|
142
|
+
req = {:url => @urls[:getobject], :read_timeout => args[:read_timeout], :headers => {}}
|
143
|
+
req[:params] = {:Resource => args[:resource], :Type => args[:type], :Location => (args[:location] ? 1 : 0), :ID => args[:id]}
|
144
|
+
if args[:accept].is_a?(Array)
|
145
|
+
req[:headers]["Accept"] = args[:accept].join(",")
|
146
|
+
else
|
147
|
+
req[:headers]["Accept"] = "image/png,image/gif,image/jpeg"
|
148
|
+
end
|
149
|
+
|
150
|
+
# Will get swapped to a streaming call rather than a download-and-parse later, easy to do as it's called with a block now
|
151
|
+
start = Time.now.utc.to_f
|
152
|
+
|
153
|
+
@request_size, @request_hash, @request_time, @rets_data = nil, nil, nil, nil
|
154
|
+
@http.request(req) do |response|
|
155
|
+
body = response.read_body
|
156
|
+
|
157
|
+
@request_time = Time.now.utc.to_f - start
|
158
|
+
@request_size, @request_hash = body.length, Digest::SHA1.hexdigest(body)
|
159
|
+
|
160
|
+
# Make sure we aren't erroring
|
161
|
+
if body =~ /(<RETS (.+)\>)/
|
162
|
+
# RETSIQ (and probably others) return a <RETS> tag on a location request without any error inside
|
163
|
+
# since parsing errors out of full image data calls is a tricky pain. We're going to keep the
|
164
|
+
# loose error checking, but will confirm that it has an actual error code
|
165
|
+
code, text = @http.get_rets_response(Nokogiri::XML($1).xpath("//RETS").first)
|
166
|
+
unless code == "0"
|
167
|
+
@rets_data = {:code => code, :text => text}
|
168
|
+
|
169
|
+
if code == "20403"
|
170
|
+
return
|
171
|
+
else
|
172
|
+
raise RETS::APIError.new("#{code}: #{text}", code, text)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Using a wildcard somewhere
|
178
|
+
if response.content_type == "multipart/parallel"
|
179
|
+
boundary = response.type_params["boundary"]
|
180
|
+
boundary.gsub!(/^"|"$/, "")
|
181
|
+
|
182
|
+
parts = body.split("--#{boundary}\r\n")
|
183
|
+
parts.last.gsub!("\r\n--#{boundary}--", "")
|
184
|
+
parts.each do |part|
|
185
|
+
part.strip!
|
186
|
+
next if part == ""
|
187
|
+
|
188
|
+
headers, content = part.split("\r\n\r\n", 2)
|
189
|
+
|
190
|
+
parsed_headers = {}
|
191
|
+
headers.split("\r\n").each do |line|
|
192
|
+
name, value = line.split(":", 2)
|
193
|
+
next unless value and value != ""
|
194
|
+
|
195
|
+
parsed_headers[name.downcase] = value.strip
|
196
|
+
end
|
197
|
+
|
198
|
+
# Check off the first children because some Rap Rets seems to use RETS-Status
|
199
|
+
# and it will include it with an object while returning actual data.
|
200
|
+
# It only does this for multipart requests, single image pulls will use <RETS> like it should.
|
201
|
+
if parsed_headers["content-type"] == "text/xml"
|
202
|
+
code, text = @http.get_rets_response(Nokogiri::XML(content).children.first)
|
203
|
+
next if code == "20403"
|
204
|
+
end
|
205
|
+
|
206
|
+
if block.arity == 1
|
207
|
+
yield parsed_headers
|
208
|
+
else
|
209
|
+
yield parsed_headers, content
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
# Either text (error) or an image of some sorts, which is irrelevant for this
|
215
|
+
else
|
216
|
+
headers = {}
|
217
|
+
GET_OBJECT_DATA.each do |field|
|
218
|
+
next unless response.header[field] and response.header[field] != ""
|
219
|
+
headers[field] = response.header[field].strip
|
220
|
+
end
|
221
|
+
|
222
|
+
if block.arity == 1
|
223
|
+
yield headers
|
224
|
+
else
|
225
|
+
yield headers, body
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
nil
|
231
|
+
end
|
232
|
+
|
233
|
+
##
|
234
|
+
# Searches the RETS server for data.
|
235
|
+
#
|
236
|
+
# @param [Hash] args
|
237
|
+
# @option args [String] :search_type What to search on, typically *Property*, *Office* or *Agent*
|
238
|
+
# @option args [String] :class What class of data to return, varies between RETS implementations and can be anything from *1* to *ResidentialProperty*
|
239
|
+
# @option args [String] :query How to filter data, should be unescaped as CGI::escape will be called on the string
|
240
|
+
# @option args [Symbol, Optional] :count_mode Either *:only* to return just the total records found or *:both* to get count and records returned
|
241
|
+
# @option args [Integer, Optional] :limit Limit total records returned
|
242
|
+
# @option args [Integer, Optional] :offset Offset to start returning records from
|
243
|
+
# @option args [Array, Optional] :select Restrict the fields the RETS server returns
|
244
|
+
# @option args [Boolean, Optional] :standard_names Whether to use standard names for all fields
|
245
|
+
# @option args [String, Optional] :restricted String to show in place of a field value for any restricted fields the user cannot see
|
246
|
+
# @option args [Integer, Optional] :read_timeout How long to wait for data from the socket before giving up
|
247
|
+
# @option args [Boolean, Optional] :disable_stream Disables the streaming setup for data and instead loads it all and then parses
|
248
|
+
#
|
249
|
+
# @yield Called for every <DATA></DATA> group from the RETS server
|
250
|
+
# @yieldparam [Hash] :data One record of data from the RETS server
|
251
|
+
#
|
252
|
+
# @raise [RETS::CapabilityNotFound]
|
253
|
+
# @raise [RETS::APIError]
|
254
|
+
# @raise [RETS::HTTPError]
|
255
|
+
# @see #rets_data
|
256
|
+
# @see #request_size
|
257
|
+
# @see #request_hash
|
258
|
+
def search(args, &block)
|
259
|
+
if !block_given? and args[:count_mode] != :only
|
260
|
+
raise ArgumentError, "No block found"
|
261
|
+
end
|
262
|
+
|
263
|
+
unless @urls[:search]
|
264
|
+
raise RETS::CapabilityNotFound.new("Cannot find URL for Search call")
|
265
|
+
end
|
266
|
+
|
267
|
+
req = {:url => @urls[:search], :read_timeout => args[:read_timeout], :disable_compression => !args[:disable_stream]}
|
268
|
+
req[:params] = {:Format => "COMPACT-DECODED", :SearchType => args[:search_type], :QueryType => "DMQL2", :Query => args[:query], :Class => args[:class], :Limit => args[:limit], :Offset => args[:offset], :RestrictedIndicator => args[:restricted]}
|
269
|
+
req[:params][:Select] = args[:select].join(",") if args[:select].is_a?(Array)
|
270
|
+
req[:params][:StandardNames] = 1 if args[:standard_names]
|
271
|
+
|
272
|
+
if args[:count_mode] == :only
|
273
|
+
req[:params][:Count] = 2
|
274
|
+
elsif args[:count_mode] == :both
|
275
|
+
req[:params][:Count] = 1
|
276
|
+
end
|
277
|
+
|
278
|
+
@request_size, @request_hash, @request_time, @rets_data = nil, nil, nil, {}
|
279
|
+
|
280
|
+
start = Time.now.utc.to_f
|
281
|
+
@http.request(req) do |response|
|
282
|
+
if args[:disable_stream]
|
283
|
+
stream = StringIO.new(response.body)
|
284
|
+
@request_time = Time.now.utc.to_f - start
|
285
|
+
else
|
286
|
+
stream = RETS::StreamHTTP.new(response)
|
287
|
+
end
|
288
|
+
|
289
|
+
sax = RETS::Base::SAXSearch.new(@rets_data, block)
|
290
|
+
Nokogiri::XML::SAX::Parser.new(sax).parse_io(stream)
|
291
|
+
|
292
|
+
if args[:disable_stream]
|
293
|
+
@request_size, @request_hash = response.body.length, Digest::SHA1.hexdigest(response.body)
|
294
|
+
else
|
295
|
+
@request_size, @request_hash = stream.size, stream.hash
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
nil
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|