Skip to content

Commit 544d311

Browse files
committed
feat: added test JSON-based scan progress persistence
- added `require 'json'` and `@json_mutex` to ensure thread-safe operations with the progress JSON file - implemented `progress_json_path`, `load_json_file`, `load_progress_from_json` and `load_todays_progress_from_json` to read coordinates and counters from `.progress.json` - added `save_progress_to_json` and wired it into `save_progress` to store `last_x`, `last_y`, `tiles_today` and scan date in JSON - implemented `update_status_in_json` and hooked JSON status updates into `update_status`, `update_all_statuses` and `update_active_status` - on initialization and in `reset_progress_for_zoom`, coordinates and statuses are now restored and synchronized from JSON when available
1 parent ef54d81 commit 544d311

1 file changed

Lines changed: 162 additions & 6 deletions

File tree

src/background_tile_loader.rb

Lines changed: 162 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'vips'
22
require 'zlib'
33
require 'stringio'
4+
require 'json'
45
require 'concurrent-ruby'
56
require_relative 'ext/terrain_downsample_extension'
67
require_relative 'vips_tile_validator'
@@ -30,6 +31,7 @@ def initialize(route, source_name)
3031
@consecutive_403_count = 0
3132
@pending_403_tiles = []
3233
@max_403_for_zoom = 1
34+
@json_mutex = Mutex.new
3335

3436
setup_progress_table
3537
load_todays_progress
@@ -193,6 +195,96 @@ def setup_progress_table
193195
end
194196
end
195197

198+
def progress_json_path
199+
mbtiles = @route[:mbtiles_file]
200+
return nil unless mbtiles
201+
202+
mbtiles.sub(/\.mbtiles$/, '.progress.json')
203+
rescue
204+
nil
205+
end
206+
207+
def load_json_file
208+
path = progress_json_path
209+
return nil unless path && File.exist?(path)
210+
211+
JSON.parse(File.read(path))
212+
rescue => e
213+
LOGGER.warn("Failed to read progress JSON: #{e}")
214+
nil
215+
end
216+
217+
def load_progress_from_json(z)
218+
@json_mutex.synchronize do
219+
data = load_json_file
220+
return nil unless data
221+
222+
zoom = data.dig('zooms', z.to_s)
223+
return nil unless zoom
224+
225+
x, y = zoom['last_x'], zoom['last_y']
226+
return nil if x.nil? || y.nil?
227+
228+
@tiles_today = [@tiles_today, zoom['tiles_today'].to_i].max if zoom['last_scan_date'] == Date.today.to_s
229+
{ x: x, y: y }
230+
end
231+
end
232+
233+
def load_todays_progress_from_json
234+
@json_mutex.synchronize do
235+
data = load_json_file
236+
return nil unless data
237+
238+
today = Date.today.to_s
239+
total = (data['zooms'] || {}).values.sum do |zoom|
240+
zoom['last_scan_date'] == today ? zoom['tiles_today'].to_i : 0
241+
end
242+
total
243+
end
244+
end
245+
246+
def save_progress_to_json(x, y, z, status: nil)
247+
path = progress_json_path
248+
return unless path
249+
250+
@json_mutex.synchronize do
251+
data = load_json_file || { 'source' => @source_name, 'zooms' => {} }
252+
data['zooms'][z.to_s] ||= {}
253+
data['zooms'][z.to_s].merge!(
254+
'last_x' => x,
255+
'last_y' => y,
256+
'tiles_today' => @tiles_today,
257+
'last_scan_date' => Date.today.to_s
258+
)
259+
data['zooms'][z.to_s]['status'] = status if status
260+
data['updated_at'] = Time.now.iso8601
261+
262+
tmp = "#{path}.tmp"
263+
File.write(tmp, JSON.generate(data))
264+
File.rename(tmp, path)
265+
end
266+
rescue => e
267+
LOGGER.warn("Failed to save progress to JSON: #{e}")
268+
end
269+
270+
def update_status_in_json(z, status)
271+
path = progress_json_path
272+
return unless path
273+
274+
@json_mutex.synchronize do
275+
data = load_json_file || { 'source' => @source_name, 'zooms' => {} }
276+
data['zooms'][z.to_s] ||= {}
277+
data['zooms'][z.to_s]['status'] = status
278+
data['updated_at'] = Time.now.iso8601
279+
280+
tmp = "#{path}.tmp"
281+
File.write(tmp, JSON.generate(data))
282+
File.rename(tmp, path)
283+
end
284+
rescue => e
285+
LOGGER.warn("Failed to update status in JSON for zoom #{z}: #{e}")
286+
end
287+
196288
def scan_zoom_level(z, token)
197289
otl_span("scan_zoom_level", {source: @source_name, zoom: z}) do
198290
if zoom_complete?(z)
@@ -481,15 +573,19 @@ def get_bounds_for_zoom(z)
481573
end
482574

483575
def load_progress(z)
576+
json_data = load_progress_from_json(z)
577+
return json_data if json_data
578+
484579
row = @route[:db][:tile_scan_progress].where(source: @source_name, zoom_level: z).first
485580
return { x: 0, y: 0 } unless row
486581

487582
@tiles_today = [@tiles_today, row[:tiles_today].to_i].max if row[:last_scan_date] == Date.today.to_s
488-
489583
{ x: row[:last_x] || 0, y: row[:last_y] || 0 }
490584
end
491585

492586
def save_progress(x, y, z)
587+
save_progress_to_json(x, y, z)
588+
493589
@route[:db][:tile_scan_progress].insert_conflict(
494590
target: [:source, :zoom_level],
495591
update: {
@@ -511,6 +607,12 @@ def save_progress(x, y, z)
511607
end
512608

513609
def load_todays_progress
610+
json_total = load_todays_progress_from_json
611+
if json_total
612+
@tiles_today = json_total
613+
return
614+
end
615+
514616
today = Date.today.to_s
515617
rows = @route[:db][:tile_scan_progress]
516618
.where(source: @source_name, last_scan_date: today)
@@ -533,6 +635,7 @@ def convert_to_webp(data)
533635

534636
def update_status(zoom_level, status)
535637
@route[:db][:tile_scan_progress].where(source: @source_name, zoom_level: zoom_level).update(status: status)
638+
update_status_in_json(zoom_level, status)
536639
rescue => e
537640
LOGGER.warn("Failed to update status for zoom #{zoom_level}: #{e}")
538641
end
@@ -541,12 +644,40 @@ def update_all_statuses(status)
541644
@route[:db][:tile_scan_progress]
542645
.where(source: @source_name, status: 'active')
543646
.update(status: status)
647+
648+
path = progress_json_path
649+
if path
650+
@json_mutex.synchronize do
651+
data = load_json_file
652+
if data
653+
data['zooms'].each_value { |z| z['status'] = status if z['status'] == 'active' }
654+
data['updated_at'] = Time.now.iso8601
655+
tmp = "#{path}.tmp"
656+
File.write(tmp, JSON.generate(data))
657+
File.rename(tmp, path)
658+
end
659+
end
660+
end
544661
rescue => e
545662
LOGGER.warn("Failed to update all statuses: #{e}")
546663
end
547664

548665
def update_active_status(status)
549666
@route[:db][:tile_scan_progress].where(source: @source_name, status: 'active').update(status: status)
667+
668+
path = progress_json_path
669+
if path
670+
@json_mutex.synchronize do
671+
data = load_json_file
672+
if data
673+
data['zooms'].each_value { |z| z['status'] = status if z['status'] == 'active' }
674+
data['updated_at'] = Time.now.iso8601
675+
tmp = "#{path}.tmp"
676+
File.write(tmp, JSON.generate(data))
677+
File.rename(tmp, path)
678+
end
679+
end
680+
end
550681
rescue => e
551682
LOGGER.warn("Failed to update active status: #{e}")
552683
end
@@ -566,18 +697,26 @@ def initialize_zoom_progress
566697
LOGGER.info("Reset #{existing[:status]} status for zoom #{z} of #{@source_name} on startup")
567698
elsif existing && existing[:status] == 'source_unavailable'
568699
@route[:db][:tile_scan_progress].where(source: @source_name, zoom_level: z).update(status: 'stopped')
700+
update_status_in_json(z, 'stopped')
569701
LOGGER.info("Reset source_unavailable status (keeping coordinates) for zoom #{z} of #{@source_name} on startup")
570702
else
703+
json_zoom = @json_mutex.synchronize { load_json_file&.dig('zooms', z.to_s) }
704+
saved_x = json_zoom&.dig('last_x') || 0
705+
saved_y = json_zoom&.dig('last_y') || 0
706+
saved_date = json_zoom&.dig('last_scan_date')
707+
saved_status = json_zoom&.dig('status') || 'waiting'
708+
restore_status = ['completed', 'stopped'].include?(saved_status) ? saved_status : 'waiting'
709+
571710
@route[:db][:tile_scan_progress].insert_conflict(
572711
target: [:source, :zoom_level]
573712
).insert(
574713
source: @source_name,
575714
zoom_level: z,
576-
last_x: 0,
577-
last_y: 0,
578-
tiles_today: 0,
579-
last_scan_date: nil,
580-
status: 'waiting'
715+
last_x: saved_x,
716+
last_y: saved_y,
717+
tiles_today: json_zoom&.dig('tiles_today') || 0,
718+
last_scan_date: saved_date,
719+
status: restore_status
581720
)
582721
end
583722
end
@@ -622,6 +761,23 @@ def reset_zoom_progress(z)
622761
last_y: 0,
623762
status: 'waiting'
624763
)
764+
765+
path = progress_json_path
766+
if path
767+
@json_mutex.synchronize do
768+
data = load_json_file || { 'source' => @source_name, 'zooms' => {} }
769+
data['zooms'][z.to_s] = (data['zooms'][z.to_s] || {}).merge(
770+
'last_x' => 0,
771+
'last_y' => 0,
772+
'status' => 'waiting'
773+
)
774+
data['updated_at'] = Time.now.iso8601
775+
tmp = "#{path}.tmp"
776+
File.write(tmp, JSON.generate(data))
777+
File.rename(tmp, path)
778+
end
779+
end
780+
625781
LOGGER.info("Reset progress for zoom #{z} of #{@source_name}")
626782
rescue => e
627783
LOGGER.warn("Failed to reset progress for zoom #{z}: #{e}")

0 commit comments

Comments
 (0)