caboose-rets 0.1.194 → 0.1.196
Sign up to get free protection for your applications and to get access to all the features.
- 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.196
|
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
|