caboose-rets 0.1.194 → 0.1.195
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/app/controllers/caboose_rets/properties_controller.rb +1 -1
- data/app/models/caboose_rets/media.rb +74 -74
- data/app/models/caboose_rets/rets_importer.rb +223 -143
- data/app/models/caboose_rets/rets_importer_old.rb +622 -0
- data/lib/caboose_rets/version.rb +1 -1
- data/lib/tasks/caboose_rets.rake +8 -4
- metadata +3 -2
@@ -0,0 +1,622 @@
|
|
1
|
+
#require 'ruby-rets'
|
2
|
+
require "rets/version"
|
3
|
+
require "rets/exceptions"
|
4
|
+
require "rets/client"
|
5
|
+
require "rets/http"
|
6
|
+
require "rets/stream_http"
|
7
|
+
require "rets/base/core"
|
8
|
+
require "rets/base/sax_search"
|
9
|
+
require "rets/base/sax_metadata"
|
10
|
+
|
11
|
+
require 'httparty'
|
12
|
+
require 'json'
|
13
|
+
|
14
|
+
# http://rets.solidearth.com/ClientHome.aspx
|
15
|
+
|
16
|
+
class CabooseRets::RetsImporter # < ActiveRecord::Base
|
17
|
+
|
18
|
+
@@rets_client = nil
|
19
|
+
@@config = nil
|
20
|
+
|
21
|
+
def self.config
|
22
|
+
return @@config
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.get_config
|
26
|
+
@@config = {
|
27
|
+
'url' => nil, # URL to the RETS login
|
28
|
+
'username' => nil,
|
29
|
+
'password' => nil,
|
30
|
+
'temp_path' => nil,
|
31
|
+
'log_file' => nil,
|
32
|
+
'media_base_url' => nil
|
33
|
+
}
|
34
|
+
config = YAML::load(File.open("#{Rails.root}/config/rets_importer.yml"))
|
35
|
+
config = config[Rails.env]
|
36
|
+
config.each { |key,val| @@config[key] = val }
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.client
|
40
|
+
self.get_config if @@config.nil? || @@config['url'].nil?
|
41
|
+
if @@rets_client.nil?
|
42
|
+
@@rets_client = RETS::Client.login(
|
43
|
+
:url => @@config['url'],
|
44
|
+
:username => @@config['username'],
|
45
|
+
:password => @@config['password']
|
46
|
+
)
|
47
|
+
end
|
48
|
+
return @@rets_client
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.meta(class_type)
|
52
|
+
case class_type
|
53
|
+
when 'Office' then Caboose::StdClass.new({ :search_type => 'Office' , :remote_key_field => 'OfficeMlsId' , :local_key_field => 'lo_mls_id' , :local_table => 'rets_offices' , :date_modified_field => 'ModificationTimestamp'})
|
54
|
+
when 'Member' then Caboose::StdClass.new({ :search_type => 'Member' , :remote_key_field => 'MemberMlsId' , :local_key_field => 'mls_id' , :local_table => 'rets_agents' , :date_modified_field => 'ModificationTimestamp'})
|
55
|
+
when 'OpenHouse' then Caboose::StdClass.new({ :search_type => 'OpenHouse' , :remote_key_field => 'OpenHouseKey' , :local_key_field => 'matrix_unique_id' , :local_table => 'rets_open_houses' , :date_modified_field => 'ModificationTimestamp'})
|
56
|
+
when 'Property' then Caboose::StdClass.new({ :search_type => 'Property' , :remote_key_field => 'ListingId' , :local_key_field => 'mls_number' , :local_table => 'rets_properties' , :date_modified_field => 'ModificationTimestamp'})
|
57
|
+
when 'Media' then Caboose::StdClass.new({ :search_type => 'Media' , :remote_key_field => 'MediaObjectID' , :local_key_field => 'media_id' , :local_table => 'rets_media' , :date_modified_field => 'ModificationTimestamp' })
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
#=============================================================================
|
62
|
+
# Import method
|
63
|
+
#=============================================================================
|
64
|
+
|
65
|
+
def self.import(class_type, query)
|
66
|
+
m = self.meta(class_type)
|
67
|
+
self.log3(class_type,nil,"Importing #{m.search_type}:#{class_type} with query #{query}...")
|
68
|
+
self.get_config if @@config.nil? || @@config['url'].nil?
|
69
|
+
params = {
|
70
|
+
:search_type => m.search_type,
|
71
|
+
:class => class_type,
|
72
|
+
:query => query,
|
73
|
+
:timeout => -1
|
74
|
+
}
|
75
|
+
obj = nil
|
76
|
+
|
77
|
+
begin
|
78
|
+
self.client.search(params) do |data|
|
79
|
+
obj = self.get_instance_with_id(class_type, data)
|
80
|
+
if obj.nil?
|
81
|
+
self.log3(class_type,nil,"Error: object is nil")
|
82
|
+
self.log3(class_type,nil,data.inspect)
|
83
|
+
next
|
84
|
+
end
|
85
|
+
obj.parse(data)
|
86
|
+
obj.save
|
87
|
+
end
|
88
|
+
rescue RETS::HTTPError => err
|
89
|
+
self.log3(class_type,nil,"Import error for #{class_type}: #{query}")
|
90
|
+
self.log3(class_type,nil,err.message)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.get_instance_with_id(class_type, data)
|
95
|
+
obj = nil
|
96
|
+
self.log "Getting instance of #{class_type}..."
|
97
|
+
m = case class_type
|
98
|
+
when 'Property' then CabooseRets::Property
|
99
|
+
when 'OpenHouse' then CabooseRets::OpenHouse
|
100
|
+
when 'Member' then CabooseRets::Agent
|
101
|
+
when 'Office' then CabooseRets::Office
|
102
|
+
when 'Media' then CabooseRets::Media
|
103
|
+
end
|
104
|
+
obj = case class_type
|
105
|
+
when 'Property' then m.where(:mls_number => data['ListingId']).exists? ? m.where(:mls_number => data['ListingId']).first : m.create(:mls_number => data['ListingId'])
|
106
|
+
when 'OpenHouse' then m.where(:matrix_unique_id => data['OpenHouseKey']).exists? ? m.where(:matrix_unique_id => data['OpenHouseKey']).first : m.create(:matrix_unique_id => data['OpenHouseKey'])
|
107
|
+
when 'Member' then m.where(:mls_id => data['MemberMlsId']).exists? ? m.where(:mls_id => data['MemberMlsId']).first : m.create(:mls_id => data['MemberMlsId'])
|
108
|
+
when 'Office' then m.where(:lo_mls_id => data['OfficeMlsId']).exists? ? m.where(:lo_mls_id => data['OfficeMlsId']).first : m.create(:lo_mls_id => data['OfficeMlsId'])
|
109
|
+
when 'Media' then m.where(:media_id => data['MediaObjectID'] ).exists? ? m.where(:media_id => data['MediaObjectID'] ).first : m.create(:media_id => data['MediaObjectID'] )
|
110
|
+
end
|
111
|
+
self.log "Found matching object ID #{obj.id}"
|
112
|
+
return obj
|
113
|
+
end
|
114
|
+
|
115
|
+
#=============================================================================
|
116
|
+
# Main updater
|
117
|
+
#=============================================================================
|
118
|
+
|
119
|
+
def self.update_after(date_modified, save_images = true)
|
120
|
+
si = save_images ? 'saving images' : 'not saving images'
|
121
|
+
self.log3(nil,nil,"Updating everything after #{date_modified} and #{si}")
|
122
|
+
self.delay(:priority => 10, :queue => 'rets').update_helper('Property' , date_modified, save_images)
|
123
|
+
self.delay(:priority => 10, :queue => 'rets').update_helper('Office' , date_modified, false)
|
124
|
+
self.delay(:priority => 10, :queue => 'rets').update_helper('Member' , date_modified, false)
|
125
|
+
self.delay(:priority => 10, :queue => 'rets').update_helper('OpenHouse', date_modified, false)
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.update_helper(class_type, date_modified, save_images = true)
|
129
|
+
si = save_images ? 'saving images' : 'not saving images'
|
130
|
+
self.log3(class_type,nil,"Updating #{class_type} modified after #{date_modified} and #{si}")
|
131
|
+
m = self.meta(class_type)
|
132
|
+
k = m.remote_key_field
|
133
|
+
d = date_modified.in_time_zone(CabooseRets::timezone).strftime("%FT%T")
|
134
|
+
|
135
|
+
statusquery = ""
|
136
|
+
case class_type
|
137
|
+
when 'Property' then statusquery = "OriginatingSystemName=WESTAL"
|
138
|
+
when 'Office' then statusquery = "OfficeStatus=Active"
|
139
|
+
when 'Member' then statusquery = "MemberStatus=Active"
|
140
|
+
when 'OpenHouse' then statusquery = "OpenHouseKeyNumeric=0+"
|
141
|
+
end
|
142
|
+
|
143
|
+
quer = "(#{m.date_modified_field}=#{d}+)AND(OriginatingSystemName=WESTAL)AND(#{statusquery})"
|
144
|
+
params = {
|
145
|
+
:search_type => m.search_type,
|
146
|
+
:class => class_type,
|
147
|
+
:select => [m.remote_key_field],
|
148
|
+
:querytype => 'DMQL2',
|
149
|
+
:query => quer,
|
150
|
+
:limit => 1000,
|
151
|
+
:standard_names_only => true,
|
152
|
+
:timeout => -1
|
153
|
+
}
|
154
|
+
self.log3(class_type,nil,"Searching with params: " + params.to_s)
|
155
|
+
self.client.search(params) do |data|
|
156
|
+
self.log3(class_type,nil,"Resulting data: " + data.to_s)
|
157
|
+
case class_type
|
158
|
+
when 'Property' then self.delay(:priority => 10, :queue => 'rets').import_properties(data[k], save_images)
|
159
|
+
when 'Office' then self.delay(:priority => 10, :queue => 'rets').import_office( data[k], false)
|
160
|
+
when 'Member' then self.delay(:priority => 10, :queue => 'rets').import_agent( data[k], false)
|
161
|
+
when 'OpenHouse' then self.delay(:priority => 10, :queue => 'rets').import_open_house(data[k], false)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Check for changed images
|
166
|
+
if class_type == 'Property' && Rails.env.production?
|
167
|
+
self.log3("Property",nil,"Checking for modified images on Properties...")
|
168
|
+
d1 = (self.last_updated - 1.hours).in_time_zone(CabooseRets::timezone).strftime("%FT%T")
|
169
|
+
params = {
|
170
|
+
:search_type => m.search_type,
|
171
|
+
:class => class_type,
|
172
|
+
:select => [m.remote_key_field],
|
173
|
+
:querytype => 'DMQL2',
|
174
|
+
:limit => 1000,
|
175
|
+
:query => "(PhotosChangeTimestamp=#{d1}+)AND(OriginatingSystemName=WESTAL)AND(MlsStatus=Active)",
|
176
|
+
:standard_names_only => true,
|
177
|
+
:timeout => -1
|
178
|
+
}
|
179
|
+
self.log3(class_type,nil,"Searching with params: " + params.to_s)
|
180
|
+
self.client.search(params) do |data|
|
181
|
+
self.log3(class_type,nil,"Resulting data: " + data.to_s)
|
182
|
+
self.delay(:priority => 10, :queue => 'rets').import_properties(data[k], true)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
#=============================================================================
|
189
|
+
# Single model import methods (called from a worker dyno)
|
190
|
+
#=============================================================================
|
191
|
+
|
192
|
+
def self.import_properties(mls_id, save_images = true)
|
193
|
+
si = save_images ? 'saving images' : 'not saving images'
|
194
|
+
self.log3('Property',mls_id,"Importing Property #{mls_id} and #{si}...")
|
195
|
+
save_images = true if !CabooseRets::Property.where(:mls_number => mls_id.to_s).exists?
|
196
|
+
self.import('Property', "(ListingId=#{mls_id})")
|
197
|
+
p = CabooseRets::Property.where(:mls_number => mls_id.to_s).first
|
198
|
+
if p != nil && p.status == 'Active'
|
199
|
+
self.download_property_images(p) if save_images == true && Rails.env.production?
|
200
|
+
if p.latitude.blank? || p.latitude == '0.0' || p.longitude.blank? || p.longitude == '0.0'
|
201
|
+
self.update_coords(p)
|
202
|
+
end
|
203
|
+
else
|
204
|
+
self.log3(nil,nil,"No Active Property associated with #{mls_id}, not downloading images")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.import_office(mls_id, save_images = true)
|
209
|
+
self.log3('Office',mls_id,"Importing Office #{mls_id}...")
|
210
|
+
self.import('Office', "(OfficeMlsId=#{mls_id})")
|
211
|
+
office = CabooseRets::Office.where(:matrix_unique_id => mls_id.to_s).first
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.import_agent(mls_id, save_images = true)
|
215
|
+
return if mls_id == "T/ISC-SA-MATRIXMONITOR"
|
216
|
+
a = CabooseRets::Agent.where(:mls_id => mls_id.to_s).first
|
217
|
+
if a.nil?
|
218
|
+
self.log3('Agent',mls_id,"Importing new Agent #{mls_id}...")
|
219
|
+
self.import('Member', "(MemberMlsId=#{mls_id})")
|
220
|
+
a = CabooseRets::Agent.where(:mls_id => mls_id.to_s).first
|
221
|
+
if a
|
222
|
+
a.last_updated = DateTime.now
|
223
|
+
a.save
|
224
|
+
end
|
225
|
+
else
|
226
|
+
lu = a.last_updated.blank? ? 0 : a.last_updated.to_time.to_i
|
227
|
+
now = DateTime.now.to_time.to_i
|
228
|
+
diff = now - lu
|
229
|
+
is_old = diff > 86400 # 24 hours
|
230
|
+
if is_old
|
231
|
+
self.log3('Agent',mls_id,"Updating existing Agent #{mls_id}...")
|
232
|
+
self.import('Member', "(MemberMlsId=#{mls_id})")
|
233
|
+
a.last_updated = DateTime.now
|
234
|
+
a.save
|
235
|
+
else
|
236
|
+
self.log3('Agent',mls_id,"Skipping importing Agent #{mls_id} because last_updated is today...")
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def self.import_open_house(oh_id, save_images = true)
|
242
|
+
self.log3('OpenHouse',oh_id,"Importing Open House #{oh_id}...")
|
243
|
+
self.import('OpenHouse', "(OpenHouseKey=#{oh_id})")
|
244
|
+
end
|
245
|
+
|
246
|
+
def self.import_media(id, save_images = true)
|
247
|
+
self.log3('Media',id,"Importing Media #{id}...")
|
248
|
+
self.import('Media', "((MediaObjectID=#{id}+),(MediaObjectID=#{id}-))")
|
249
|
+
end
|
250
|
+
|
251
|
+
#=============================================================================
|
252
|
+
# Images go here
|
253
|
+
#=============================================================================
|
254
|
+
|
255
|
+
def self.download_property_images(p)
|
256
|
+
return if Rails.env.development?
|
257
|
+
self.log3('Property',p.mls_number,"Downloading images for #{p.mls_number}...")
|
258
|
+
ids_to_keep = []
|
259
|
+
begin
|
260
|
+
self.client.get_object(:resource => 'Property', :type => 'Photo', :location => false, :id => "#{p.matrix_unique_id}:*") do |headers, content|
|
261
|
+
next if headers.blank?
|
262
|
+
ind = headers['orderhint'] ? headers['orderhint'].to_i : 1
|
263
|
+
self.log3('Media',p.mls_number,headers.to_s)
|
264
|
+
self.log3('Media',p.mls_number,"Downloading photo with content-id #{headers['content-id']}, index #{ind}")
|
265
|
+
is_new = false
|
266
|
+
m = CabooseRets::Media.where(:media_mui => headers['content-id'], :media_order => ind).first
|
267
|
+
is_new = true if m.nil?
|
268
|
+
m = CabooseRets::Media.new if m.nil?
|
269
|
+
tmp_path = "#{Rails.root}/tmp/rets_media_#{headers['content-id']}_#{ind}.jpeg"
|
270
|
+
File.open(tmp_path, "wb") do |f|
|
271
|
+
f.write(content)
|
272
|
+
end
|
273
|
+
m.media_mui = headers['content-id']
|
274
|
+
m.media_order = ind
|
275
|
+
m.media_type = 'Photo'
|
276
|
+
cm = nil
|
277
|
+
old_cm_id = is_new ? nil : m.media_id
|
278
|
+
begin
|
279
|
+
cm = Caboose::Media.new
|
280
|
+
cm.image = File.open(tmp_path)
|
281
|
+
cm.name = "rets_media_#{headers['content-id']}_#{ind}"
|
282
|
+
cm.original_name = "rets_media_#{headers['content-id']}_#{ind}.jpeg"
|
283
|
+
cm.processed = true
|
284
|
+
cm.save
|
285
|
+
if cm && !cm.id.blank?
|
286
|
+
m.media_id = cm.id
|
287
|
+
m.save
|
288
|
+
ids_to_keep << m.id
|
289
|
+
if is_new
|
290
|
+
self.log3("Media",p.mls_number,"Created new RetsMedia object #{m.id}, media_id = #{m.media_id}")
|
291
|
+
else
|
292
|
+
old_media = Caboose::Media.where(:id => old_cm_id).first
|
293
|
+
if old_media
|
294
|
+
self.log3("Media",p.mls_number,"Deleting old CabooseMedia #{old_media.id}")
|
295
|
+
old_media.destroy
|
296
|
+
end
|
297
|
+
self.log3("Media",p.mls_number,"RetsMedia object already existed #{m.id}, updated media_id = #{m.media_id}")
|
298
|
+
end
|
299
|
+
self.log3("Media",p.mls_number,"Image rets_media_#{headers['content-id']}_#{ind} saved")
|
300
|
+
else
|
301
|
+
self.log3("Media",p.mls_number,"CabooseMedia was not created for some reason, not saving RetsMedia")
|
302
|
+
end
|
303
|
+
rescue
|
304
|
+
self.log3("Media",p.mls_number,"Error processing image #{ind} from RETS")
|
305
|
+
end
|
306
|
+
`rm #{tmp_path}`
|
307
|
+
end
|
308
|
+
rescue
|
309
|
+
self.log3("Media",p.mls_number,"Error downloading images for property with MLS # #{p.mls_number}")
|
310
|
+
end
|
311
|
+
|
312
|
+
# If we downloaded new images, look for old images to delete.
|
313
|
+
if ids_to_keep.count > 0
|
314
|
+
self.log3("Media",p.mls_number,"Keeping new RetsMedia ids: #{ids_to_keep}")
|
315
|
+
self.log3("Media",p.mls_number,"Looking for old RetsMedia to delete")
|
316
|
+
CabooseRets::Media.where(:media_mui => p.matrix_unique_id).where("id not in (?)",ids_to_keep).each do |med|
|
317
|
+
self.log3("Media",p.mls_number,"Deleting old RetsMedia #{med.id} and CabooseMedia #{med.media_id}...")
|
318
|
+
m = Caboose::Media.where(:id => med.media_id).where("name ILIKE ?","rets_media%").first
|
319
|
+
m.destroy if m
|
320
|
+
med.destroy
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
325
|
+
|
326
|
+
def self.download_missing_images
|
327
|
+
self.log3("Property",nil,"Downloading all missing images...")
|
328
|
+
CabooseRets::Property.where("photo_count = ? OR photo_count is null", '').where(:status => "Active").all.each do |p|
|
329
|
+
self.delay(:priority => 10, :queue => 'rets').import_properties(p.mls_number, true)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def self.download_agent_image(agent)
|
334
|
+
|
335
|
+
end
|
336
|
+
|
337
|
+
def self.download_office_image(office)
|
338
|
+
|
339
|
+
end
|
340
|
+
|
341
|
+
#=============================================================================
|
342
|
+
# GPS
|
343
|
+
#=============================================================================
|
344
|
+
|
345
|
+
def self.update_coords(p = nil)
|
346
|
+
if p.nil?
|
347
|
+
model = CabooseRets::Property
|
348
|
+
i = 0
|
349
|
+
self.log3('Property',nil,"Updating coords properties...")
|
350
|
+
model.where(:latitude => nil).reorder(:mls_number).each do |p1|
|
351
|
+
self.delay(:priority => 10, :queue => 'rets').update_coords(p1)
|
352
|
+
end
|
353
|
+
return
|
354
|
+
end
|
355
|
+
|
356
|
+
self.log3('Property',p.mls_number,"Getting coords for MLS # #{p.mls_number}...")
|
357
|
+
coords = self.coords_from_address(CGI::escape "#{p.street_number} #{p.street_name}, #{p.city}, #{p.state_or_province} #{p.postal_code}")
|
358
|
+
if coords.nil? || coords == false
|
359
|
+
self.log3('Property',nil,"Can't set coords for MLS # #{p.mls_number}...")
|
360
|
+
return
|
361
|
+
end
|
362
|
+
|
363
|
+
p.latitude = coords['lat'].to_f
|
364
|
+
p.longitude = coords['lng'].to_f
|
365
|
+
p.save
|
366
|
+
end
|
367
|
+
|
368
|
+
def self.coords_from_address(address)
|
369
|
+
begin
|
370
|
+
uri = "https://maps.googleapis.com/maps/api/geocode/json?key=AIzaSyB9Wwx7sdWaUnFyLcdQ61NOV7DE2NZkDUE&address=#{address}"
|
371
|
+
uri.gsub!(" ", "+")
|
372
|
+
resp = HTTParty.get(uri)
|
373
|
+
json = JSON.parse(resp.body)
|
374
|
+
return json['results'][0]['geometry']['location']
|
375
|
+
rescue
|
376
|
+
self.log "Error: #{uri}"
|
377
|
+
sleep(2)
|
378
|
+
return false
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
#=============================================================================
|
383
|
+
# Purging
|
384
|
+
#=============================================================================
|
385
|
+
|
386
|
+
def self.purge
|
387
|
+
self.log3(nil,nil,'purging')
|
388
|
+
self.purge_properties
|
389
|
+
self.purge_offices
|
390
|
+
self.purge_agents
|
391
|
+
self.purge_open_houses
|
392
|
+
end
|
393
|
+
|
394
|
+
def self.purge_properties() self.delay(:priority => 10, :queue => 'rets').purge_helper('Property', '2012-01-01') end
|
395
|
+
def self.purge_offices() self.delay(:priority => 10, :queue => 'rets').purge_helper('Office', '2012-01-01') end
|
396
|
+
def self.purge_agents() self.delay(:priority => 10, :queue => 'rets').purge_helper('Member', '2012-01-01') end
|
397
|
+
def self.purge_open_houses() self.delay(:priority => 10, :queue => 'rets').purge_helper('OpenHouse', '2012-01-01') end
|
398
|
+
|
399
|
+
|
400
|
+
# Adds/removes records in the database
|
401
|
+
def self.purge_helper(class_type, date_modified)
|
402
|
+
m = self.meta(class_type)
|
403
|
+
self.log(m.search_type)
|
404
|
+
|
405
|
+
self.log3(class_type,nil,"Purging #{class_type}...")
|
406
|
+
|
407
|
+
# Get the total number of records
|
408
|
+
self.log3(class_type,nil,"Getting total number of records for #{class_type}...")
|
409
|
+
|
410
|
+
statusquery = ""
|
411
|
+
|
412
|
+
case class_type
|
413
|
+
when 'Property' then statusquery = "MlsStatus=Active"
|
414
|
+
when 'Office' then statusquery = "OfficeStatus=Active"
|
415
|
+
when 'Member' then statusquery = "MemberStatus=Active"
|
416
|
+
when 'OpenHouse' then statusquery = "OpenHouseKeyNumeric=0+"
|
417
|
+
end
|
418
|
+
|
419
|
+
params = {
|
420
|
+
:search_type => m.search_type,
|
421
|
+
:class => class_type,
|
422
|
+
:query => "(#{m.date_modified_field}=#{date_modified}T00:00:01+)AND(#{statusquery})AND(OriginatingSystemName=WESTAL)",
|
423
|
+
:standard_names_only => true,
|
424
|
+
:limit => 1000,
|
425
|
+
:timeout => -1
|
426
|
+
}
|
427
|
+
count = 0
|
428
|
+
self.client.search(params.merge({ :count => 1})) do |data| end
|
429
|
+
count = self.client.rets_data[:code] == "20201" ? 0 : self.client.rets_data[:count]
|
430
|
+
batch_count = (count.to_f/1000.0).ceil
|
431
|
+
|
432
|
+
ids = []
|
433
|
+
k = m.remote_key_field
|
434
|
+
(0...batch_count).each do |i|
|
435
|
+
self.log3(class_type,nil,"Getting ids for #{class_type} (batch #{i+1} of #{batch_count})...")
|
436
|
+
self.client.search(params.merge({ :select => [k], :limit => 1000, :offset => 1000*i })) do |data|
|
437
|
+
ids << data[k]
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
# Only do stuff if we got a real response from the server
|
442
|
+
if ids.count > 0
|
443
|
+
|
444
|
+
self.log3(class_type,nil,"Remote IDs found: #{ids.to_s}")
|
445
|
+
|
446
|
+
# Delete any records in the local database that shouldn't be there
|
447
|
+
self.log3(class_type,nil,"Finding #{class_type} records in the local database that are not in the remote database...")
|
448
|
+
t = m.local_table
|
449
|
+
k = m.local_key_field
|
450
|
+
query = "select distinct #{k} from #{t}"
|
451
|
+
rows = ActiveRecord::Base.connection.select_all(ActiveRecord::Base.send(:sanitize_sql_array, query))
|
452
|
+
local_ids = rows.collect{ |row| row[k] }
|
453
|
+
self.log3(class_type,nil,"Local IDs found: #{local_ids.to_s}")
|
454
|
+
ids_to_remove = local_ids - ids
|
455
|
+
self.log3(class_type,nil,"Found #{ids_to_remove.count} #{class_type} records in the local database that are not in the remote database.")
|
456
|
+
|
457
|
+
# Delete all RetsMedia and CabooseMedia for the deleted property listings (except keep the first image)
|
458
|
+
if class_type == 'Property' && ids_to_remove && ids_to_remove.count > 0
|
459
|
+
self.log3(class_type,nil,"Deleting Media objects that shouldn't be there...")
|
460
|
+
muis = CabooseRets::Property.where("#{k} in (?)", ids_to_remove).pluck(:matrix_unique_id)
|
461
|
+
if muis && muis.count > 0 && Rails.env.production?
|
462
|
+
CabooseRets::Media.where("media_mui in (?)", muis).where("media_order != ?", 1).each do |med|
|
463
|
+
self.log3("Media",med.id,"Deleting old RetsMedia #{med.id} and CabooseMedia #{med.media_id}...")
|
464
|
+
m = Caboose::Media.where(:id => med.media_id).where("name ILIKE ?","rets_media%").first
|
465
|
+
m.destroy if m
|
466
|
+
med.destroy
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
if class_type != 'Property' # keep all properties in the DB
|
472
|
+
self.log3(class_type,nil,"Deleting #{class_type} records in the local database that shouldn't be there...")
|
473
|
+
query = ["delete from #{t} where #{k} in (?)", ids_to_remove]
|
474
|
+
ActiveRecord::Base.connection.execute(ActiveRecord::Base.send(:sanitize_sql_array, query))
|
475
|
+
else # mark deleted properties as Deleted status
|
476
|
+
self.log3(class_type,nil,"Setting deleted properties as Deleted status...")
|
477
|
+
query = ["update #{t} set status = ? where #{k} in (?)", "Deleted", ids_to_remove]
|
478
|
+
ActiveRecord::Base.connection.execute(ActiveRecord::Base.send(:sanitize_sql_array, query))
|
479
|
+
end
|
480
|
+
|
481
|
+
# Find any ids in the remote database that should be in the local database
|
482
|
+
self.log3(class_type,nil,"Finding #{class_type} records in the remote database that should be in the local database...")
|
483
|
+
query = "select distinct #{k} from #{t}"
|
484
|
+
rows = ActiveRecord::Base.connection.select_all(ActiveRecord::Base.send(:sanitize_sql_array, query))
|
485
|
+
local_ids = rows.collect{ |row| row[k] }
|
486
|
+
self.log3(class_type,nil,"Local IDs found: #{local_ids.to_s}")
|
487
|
+
ids_to_add = ids - local_ids
|
488
|
+
ids_to_add = ids_to_add.sort.reverse
|
489
|
+
self.log3(class_type,nil,"Found #{ids_to_add.count} #{class_type} records in the remote database that we need to add to the local database.")
|
490
|
+
ids_to_add.each do |id|
|
491
|
+
self.log3(class_type,nil,"Importing #{id}...")
|
492
|
+
case class_type
|
493
|
+
when "Property" then self.delay(:priority => 10, :queue => 'rets').import_properties(id, true)
|
494
|
+
when "Office" then self.delay(:priority => 10, :queue => 'rets').import_office(id, false)
|
495
|
+
when "Member" then self.delay(:priority => 10, :queue => 'rets').import_agent(id, false)
|
496
|
+
when "OpenHouse" then self.delay(:priority => 10, :queue => 'rets').import_open_house(id, false)
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
#=============================================================================
|
503
|
+
# Logging
|
504
|
+
#=============================================================================
|
505
|
+
|
506
|
+
def self.log(msg)
|
507
|
+
puts "[rets_importer] #{msg}"
|
508
|
+
end
|
509
|
+
|
510
|
+
def self.log2(msg)
|
511
|
+
puts "======================================================================"
|
512
|
+
puts "[rets_importer] #{msg}"
|
513
|
+
puts "======================================================================"
|
514
|
+
end
|
515
|
+
|
516
|
+
def self.log3(class_name, object_id, text)
|
517
|
+
self.log2(text)
|
518
|
+
log = CabooseRets::Log.new
|
519
|
+
log.class_name = class_name
|
520
|
+
log.object_id = object_id
|
521
|
+
log.text = text
|
522
|
+
log.timestamp = DateTime.now
|
523
|
+
log.save
|
524
|
+
end
|
525
|
+
|
526
|
+
#=============================================================================
|
527
|
+
# Locking update task
|
528
|
+
#=============================================================================
|
529
|
+
|
530
|
+
def self.update_rets
|
531
|
+
self.log3(nil,nil,"Updating rets...")
|
532
|
+
if self.task_is_locked
|
533
|
+
self.log2("Task is locked, aborting.")
|
534
|
+
return
|
535
|
+
end
|
536
|
+
self.log2("Locking task...")
|
537
|
+
task_started = self.lock_task
|
538
|
+
begin
|
539
|
+
overlap = 2.hours
|
540
|
+
if (DateTime.now - self.last_purged).to_f >= 0.5
|
541
|
+
self.purge
|
542
|
+
self.save_last_purged(task_started)
|
543
|
+
overlap = 1.week
|
544
|
+
end
|
545
|
+
self.update_after((self.last_updated - overlap), false)
|
546
|
+
self.download_missing_images if Rails.env.production?
|
547
|
+
self.log3(nil,nil,"Saving the timestamp for when we updated to #{task_started.to_s}...")
|
548
|
+
self.save_last_updated(task_started)
|
549
|
+
self.log2("Unlocking the task...")
|
550
|
+
self.unlock_task
|
551
|
+
rescue Exception => err
|
552
|
+
puts err
|
553
|
+
raise
|
554
|
+
ensure
|
555
|
+
self.log2("Unlocking task if last updated...")
|
556
|
+
self.unlock_task_if_last_updated(task_started)
|
557
|
+
end
|
558
|
+
# Start the same update process in 20 minutes
|
559
|
+
self.log3(nil,nil,"Adding the update rets task for 20 minutes from now...")
|
560
|
+
q = "handler like '%update_rets%'"
|
561
|
+
count = Delayed::Job.where(q).count
|
562
|
+
if count == 0 || (count == 1 && Delayed::Job.where(q).first.locked_at)
|
563
|
+
self.delay(:run_at => 20.minutes.from_now, :priority => 10, :queue => 'rets').update_rets
|
564
|
+
end
|
565
|
+
|
566
|
+
# Delete RETS logs over 7 days old
|
567
|
+
dt = DateTime.now - 5.days
|
568
|
+
sql = "delete from rets_logs where timestamp < '#{dt}';"
|
569
|
+
ActiveRecord::Base.connection.select_all(sql)
|
570
|
+
|
571
|
+
# Update search options
|
572
|
+
CabooseRets::SearchOption.delay(:queue => "rets", :priority => 15).update_search_options
|
573
|
+
end
|
574
|
+
|
575
|
+
def self.last_updated
|
576
|
+
if !Caboose::Setting.exists?(:name => 'rets_last_updated')
|
577
|
+
Caboose::Setting.create(:name => 'rets_last_updated', :value => '2013-08-06T00:00:01')
|
578
|
+
end
|
579
|
+
s = Caboose::Setting.where(:name => 'rets_last_updated').first
|
580
|
+
return DateTime.parse(s.value)
|
581
|
+
end
|
582
|
+
|
583
|
+
def self.last_purged
|
584
|
+
if !Caboose::Setting.exists?(:name => 'rets_last_purged')
|
585
|
+
Caboose::Setting.create(:name => 'rets_last_purged', :value => '2013-08-06T00:00:01')
|
586
|
+
end
|
587
|
+
s = Caboose::Setting.where(:name => 'rets_last_purged').first
|
588
|
+
return DateTime.parse(s.value)
|
589
|
+
end
|
590
|
+
|
591
|
+
def self.save_last_updated(d)
|
592
|
+
s = Caboose::Setting.where(:name => 'rets_last_updated').first
|
593
|
+
s.value = d.in_time_zone(CabooseRets::timezone).strftime("%FT%T%:z")
|
594
|
+
s.save
|
595
|
+
end
|
596
|
+
|
597
|
+
def self.save_last_purged(d)
|
598
|
+
s = Caboose::Setting.where(:name => 'rets_last_purged').first
|
599
|
+
s.value = d.in_time_zone(CabooseRets::timezone).strftime("%FT%T%:z")
|
600
|
+
s.save
|
601
|
+
end
|
602
|
+
|
603
|
+
def self.task_is_locked
|
604
|
+
return Caboose::Setting.exists?(:name => 'rets_update_running')
|
605
|
+
end
|
606
|
+
|
607
|
+
def self.lock_task
|
608
|
+
d = DateTime.now.utc.in_time_zone(CabooseRets::timezone)
|
609
|
+
Caboose::Setting.create(:name => 'rets_update_running', :value => d.strftime("%FT%T%:z"))
|
610
|
+
return d
|
611
|
+
end
|
612
|
+
|
613
|
+
def self.unlock_task
|
614
|
+
Caboose::Setting.where(:name => 'rets_update_running').first.destroy
|
615
|
+
end
|
616
|
+
|
617
|
+
def self.unlock_task_if_last_updated(d)
|
618
|
+
setting = Caboose::Setting.where(:name => 'rets_update_running').first
|
619
|
+
self.unlock_task if setting && d.in_time_zone.strftime("%FT%T%:z") == setting.value
|
620
|
+
end
|
621
|
+
|
622
|
+
end
|
data/lib/caboose_rets/version.rb
CHANGED
data/lib/tasks/caboose_rets.rake
CHANGED
@@ -211,10 +211,14 @@ namespace :caboose_rets do
|
|
211
211
|
|
212
212
|
desc "Import rets data"
|
213
213
|
task :import => :environment do
|
214
|
-
CabooseRets::RetsImporter.import('Member' , "
|
215
|
-
CabooseRets::RetsImporter.import('Property' , "
|
216
|
-
CabooseRets::RetsImporter.import('Office' , "
|
217
|
-
CabooseRets::RetsImporter.import('OpenHouse' , "
|
214
|
+
CabooseRets::RetsImporter.import('Member' , "MemberStatus eq 'Active'")
|
215
|
+
CabooseRets::RetsImporter.import('Property' , "MlsStatus eq 'Active'")
|
216
|
+
CabooseRets::RetsImporter.import('Office' , "OfficeStatus eq 'Active'")
|
217
|
+
CabooseRets::RetsImporter.import('OpenHouse' , "OpenHouseKeyNumeric gt 0")
|
218
|
+
end
|
219
|
+
|
220
|
+
task :test_import => :environment do
|
221
|
+
CabooseRets::RetsImporter.import_one_property('146546')
|
218
222
|
end
|
219
223
|
|
220
224
|
desc "Single Import Test"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: caboose-rets
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.195
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William Barry
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: caboose-cms
|
@@ -78,6 +78,7 @@ files:
|
|
78
78
|
- app/models/caboose_rets/property.rb
|
79
79
|
- app/models/caboose_rets/rets_config.rb
|
80
80
|
- app/models/caboose_rets/rets_importer.rb
|
81
|
+
- app/models/caboose_rets/rets_importer_old.rb
|
81
82
|
- app/models/caboose_rets/rets_plugin.rb
|
82
83
|
- app/models/caboose_rets/saved_property.rb
|
83
84
|
- app/models/caboose_rets/saved_search.rb
|