caboose-rets 0.1.194 → 0.1.195

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,3 +1,3 @@
1
1
  module CabooseRets
2
- VERSION = '0.1.194'
2
+ VERSION = '0.1.195'
3
3
  end
@@ -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' , "(MemberStatus=Active)")
215
- CabooseRets::RetsImporter.import('Property' , "(MlsStatus=Active)")
216
- CabooseRets::RetsImporter.import('Office' , "(OfficeStatus=Active)")
217
- CabooseRets::RetsImporter.import('OpenHouse' , "(OpenHouseKeyNumeric=0+)")
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.194
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: 2022-03-30 00:00:00.000000000 Z
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