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