tapsoob 0.6.2-java → 0.7.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +18 -2
- data/lib/tapsoob/cli/data_stream.rb +3 -3
- data/lib/tapsoob/cli/root.rb +2 -3
- data/lib/tapsoob/data_stream/base.rb +315 -0
- data/lib/tapsoob/data_stream/file_partition.rb +87 -0
- data/lib/tapsoob/data_stream/interleaved.rb +80 -0
- data/lib/tapsoob/data_stream/keyed.rb +124 -0
- data/lib/tapsoob/data_stream/keyed_partition.rb +64 -0
- data/lib/tapsoob/data_stream.rb +7 -378
- data/lib/tapsoob/operation/base.rb +240 -0
- data/lib/tapsoob/operation/pull.rb +419 -0
- data/lib/tapsoob/operation/push.rb +446 -0
- data/lib/tapsoob/operation.rb +5 -664
- data/lib/tapsoob/progress/bar.rb +0 -4
- data/lib/tapsoob/progress/multi_bar.rb +90 -58
- data/lib/tapsoob/progress/thread_safe_bar.rb +0 -3
- data/lib/tapsoob/progress_event.rb +109 -0
- data/lib/tapsoob/version.rb +1 -1
- data/lib/tasks/tapsoob.rake +2 -2
- metadata +11 -2
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Tapsoob
|
|
4
4
|
module Progress
|
|
5
|
-
# MultiBar manages multiple progress bars in parallel
|
|
6
|
-
#
|
|
5
|
+
# MultiBar manages multiple progress bars in parallel with a clean interface:
|
|
6
|
+
# - N progress bar lines (constantly updating)
|
|
7
|
+
# - 1 separator line
|
|
8
|
+
# - 1 info message line (shows latest INFO, gets replaced)
|
|
7
9
|
class MultiBar
|
|
8
10
|
def initialize(max_bars = 4)
|
|
9
11
|
@max_bars = max_bars
|
|
@@ -12,13 +14,33 @@ module Tapsoob
|
|
|
12
14
|
@active = true
|
|
13
15
|
@out = STDOUT
|
|
14
16
|
@last_update = Time.now
|
|
15
|
-
@reserved_lines = 0 # Track how many lines we've actually reserved
|
|
16
17
|
@max_title_width = 14 # Minimum width, will grow with longer titles
|
|
18
|
+
@initialized = false
|
|
19
|
+
@total_lines = 0 # Total lines: max_bars + separator + info line
|
|
20
|
+
@info_message = "" # Current info message to display
|
|
21
|
+
@start_time = Time.now # Track total elapsed time
|
|
22
|
+
@terminal_width = get_terminal_width
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Get terminal width, default to 160 if can't detect
|
|
26
|
+
def get_terminal_width
|
|
27
|
+
require 'io/console'
|
|
28
|
+
IO.console&.winsize&.[](1) || 160
|
|
29
|
+
rescue
|
|
30
|
+
160
|
|
17
31
|
end
|
|
18
32
|
|
|
19
33
|
# Create a new progress bar and return it
|
|
20
34
|
def create_bar(title, total)
|
|
21
35
|
@mutex.synchronize do
|
|
36
|
+
# Initialize display area on first bar creation
|
|
37
|
+
unless @initialized
|
|
38
|
+
@total_lines = @max_bars + 2 # bars + separator + info line
|
|
39
|
+
@total_lines.times { @out.print "\n" }
|
|
40
|
+
@out.flush
|
|
41
|
+
@initialized = true
|
|
42
|
+
end
|
|
43
|
+
|
|
22
44
|
# Remove any existing bar with the same title to prevent duplicates
|
|
23
45
|
@bars.reject! { |b| b.title == title }
|
|
24
46
|
|
|
@@ -26,20 +48,20 @@ module Tapsoob
|
|
|
26
48
|
@max_title_width = [@max_title_width, title.length].max
|
|
27
49
|
|
|
28
50
|
bar = ThreadSafeBar.new(title, total, self)
|
|
29
|
-
|
|
30
|
-
# Reserve a line for this new bar during active updates
|
|
31
|
-
# Cap at 2 * max_bars to show active workers + some recent finished bars
|
|
32
|
-
if @reserved_lines < @max_bars * 2
|
|
33
|
-
@out.print "\n"
|
|
34
|
-
@out.flush
|
|
35
|
-
@reserved_lines += 1
|
|
36
|
-
end
|
|
37
|
-
|
|
38
51
|
@bars << bar
|
|
39
52
|
bar
|
|
40
53
|
end
|
|
41
54
|
end
|
|
42
55
|
|
|
56
|
+
# Update the info message line (called from outside for INFO logs)
|
|
57
|
+
def set_info(message)
|
|
58
|
+
@mutex.synchronize do
|
|
59
|
+
return unless @active
|
|
60
|
+
@info_message = message
|
|
61
|
+
redraw_all if @initialized
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
43
65
|
# Get the current maximum title width for alignment
|
|
44
66
|
# Note: Always called from within synchronized methods, so no mutex needed
|
|
45
67
|
def max_title_width
|
|
@@ -73,18 +95,26 @@ module Tapsoob
|
|
|
73
95
|
end
|
|
74
96
|
end
|
|
75
97
|
|
|
76
|
-
# Stop all progress bars and
|
|
98
|
+
# Stop all progress bars and clear them from display
|
|
77
99
|
def stop
|
|
78
100
|
@mutex.synchronize do
|
|
101
|
+
return unless @active # Already stopped
|
|
79
102
|
@active = false
|
|
80
103
|
|
|
81
|
-
#
|
|
82
|
-
|
|
104
|
+
# Clear all lines (progress bars + separator + info line)
|
|
105
|
+
if @total_lines > 0 && @initialized
|
|
106
|
+
# Move cursor up to first line
|
|
107
|
+
@out.print "\e[#{@total_lines}A"
|
|
108
|
+
|
|
109
|
+
# Clear each line
|
|
110
|
+
@total_lines.times do
|
|
111
|
+
@out.print "\r\e[2K\n"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Move cursor back to start
|
|
115
|
+
@out.print "\e[#{@total_lines}A\r"
|
|
116
|
+
end
|
|
83
117
|
|
|
84
|
-
# Final redraw to show completed state (skip active check)
|
|
85
|
-
redraw_all(true)
|
|
86
|
-
# Move cursor past all bars
|
|
87
|
-
@out.print "\n"
|
|
88
118
|
@out.flush
|
|
89
119
|
end
|
|
90
120
|
end
|
|
@@ -97,62 +127,64 @@ module Tapsoob
|
|
|
97
127
|
end
|
|
98
128
|
|
|
99
129
|
def redraw_all(force = false)
|
|
100
|
-
return unless
|
|
130
|
+
return unless @active
|
|
101
131
|
return if @bars.empty?
|
|
102
132
|
|
|
103
|
-
|
|
104
|
-
render_final_display
|
|
105
|
-
else
|
|
106
|
-
render_active_display
|
|
107
|
-
end
|
|
133
|
+
render_active_display
|
|
108
134
|
end
|
|
109
135
|
|
|
110
|
-
#
|
|
111
|
-
def
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
# Print all bars (adds new lines as needed)
|
|
119
|
-
@bars.each do |bar|
|
|
120
|
-
@out.print "\r\e[K"
|
|
121
|
-
bar.render_to(@out)
|
|
122
|
-
@out.print "\n"
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
@out.flush
|
|
136
|
+
# Format elapsed time as HH:MM:SS
|
|
137
|
+
def format_elapsed_time
|
|
138
|
+
elapsed = Time.now - @start_time
|
|
139
|
+
hours = (elapsed / 3600).to_i
|
|
140
|
+
minutes = ((elapsed % 3600) / 60).to_i
|
|
141
|
+
seconds = (elapsed % 60).to_i
|
|
142
|
+
sprintf("%02d:%02d:%02d", hours, minutes, seconds)
|
|
126
143
|
end
|
|
127
144
|
|
|
128
|
-
#
|
|
145
|
+
# Render the complete display: progress bars + separator + info line
|
|
129
146
|
def render_active_display
|
|
130
|
-
return if @
|
|
147
|
+
return if @total_lines == 0
|
|
131
148
|
|
|
132
|
-
#
|
|
133
|
-
|
|
149
|
+
# Show the last N bars (finished or not) - creates a rolling window effect
|
|
150
|
+
# As new tables start, old completed ones scroll off the top
|
|
151
|
+
bars_to_draw = @bars.last(@max_bars)
|
|
134
152
|
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
remaining_space = [@reserved_lines - active_bars.length, 0].max
|
|
138
|
-
bars_to_draw = active_bars + finished_bars.last(remaining_space)
|
|
153
|
+
# Move cursor up to first line
|
|
154
|
+
@out.print "\e[#{@total_lines}A"
|
|
139
155
|
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
# Move up and redraw in reserved space
|
|
144
|
-
@out.print "\r\e[#{@reserved_lines}A"
|
|
145
|
-
@reserved_lines.times do |i|
|
|
156
|
+
# Draw progress bars (they handle their own width)
|
|
157
|
+
@max_bars.times do |i|
|
|
146
158
|
@out.print "\r\e[K"
|
|
147
159
|
bars_to_draw[i].render_to(@out) if i < bars_to_draw.length
|
|
148
160
|
@out.print "\n"
|
|
149
161
|
end
|
|
150
162
|
|
|
163
|
+
# Draw separator line using box drawing character
|
|
164
|
+
@out.print "\r\e[K"
|
|
165
|
+
@out.print "─" * @terminal_width
|
|
166
|
+
@out.print "\n"
|
|
167
|
+
|
|
168
|
+
# Draw info message line with elapsed time on the right
|
|
169
|
+
@out.print "\r\e[K"
|
|
170
|
+
unless @info_message.empty?
|
|
171
|
+
elapsed_str = "Elapsed: #{format_elapsed_time}"
|
|
172
|
+
# Calculate space to right-align elapsed time
|
|
173
|
+
available_width = @terminal_width - @info_message.length - elapsed_str.length - 2
|
|
174
|
+
if available_width > 0
|
|
175
|
+
@out.print @info_message
|
|
176
|
+
@out.print " " * available_width
|
|
177
|
+
@out.print elapsed_str
|
|
178
|
+
else
|
|
179
|
+
# If too long, just show message
|
|
180
|
+
@out.print @info_message[0...(@terminal_width - elapsed_str.length - 2)]
|
|
181
|
+
@out.print " " + elapsed_str
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
@out.print "\n"
|
|
185
|
+
|
|
151
186
|
@out.flush
|
|
152
187
|
end
|
|
153
188
|
end
|
|
154
189
|
end
|
|
155
190
|
end
|
|
156
|
-
|
|
157
|
-
# Backward compatibility alias
|
|
158
|
-
MultiProgressBar = Tapsoob::Progress::MultiBar
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
module Tapsoob
|
|
5
|
+
module ProgressEvent
|
|
6
|
+
@last_progress_time = {}
|
|
7
|
+
@progress_throttle = 0.5 # Emit progress at most every 0.5 seconds per table
|
|
8
|
+
@enabled = false # Only emit when CLI progress bars are disabled
|
|
9
|
+
|
|
10
|
+
def self.enabled=(value)
|
|
11
|
+
@enabled = value
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.enabled?
|
|
15
|
+
@enabled
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Emit structured JSON progress events to STDERR for machine parsing
|
|
19
|
+
# Only emits when enabled (typically when CLI progress bars are disabled)
|
|
20
|
+
def self.emit(event_type, data = {})
|
|
21
|
+
return unless @enabled
|
|
22
|
+
event = {
|
|
23
|
+
event: event_type,
|
|
24
|
+
timestamp: Time.now.utc.iso8601
|
|
25
|
+
}.merge(data)
|
|
26
|
+
|
|
27
|
+
STDERR.puts "PROGRESS: #{JSON.generate(event)}"
|
|
28
|
+
STDERR.flush
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Check if enough time has passed to emit a progress event for this table
|
|
32
|
+
def self.should_emit_progress?(table_name)
|
|
33
|
+
now = Time.now
|
|
34
|
+
last_time = @last_progress_time[table_name]
|
|
35
|
+
|
|
36
|
+
if last_time.nil? || (now - last_time) >= @progress_throttle
|
|
37
|
+
@last_progress_time[table_name] = now
|
|
38
|
+
true
|
|
39
|
+
else
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Clear throttle state for a table (call when table completes)
|
|
45
|
+
def self.clear_throttle(table_name)
|
|
46
|
+
@last_progress_time.delete(table_name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Schema events
|
|
50
|
+
def self.schema_start(table_count)
|
|
51
|
+
emit('schema_start', tables: table_count)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.schema_complete(table_count)
|
|
55
|
+
emit('schema_complete', tables: table_count)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Data events
|
|
59
|
+
def self.data_start(table_count, record_count)
|
|
60
|
+
emit('data_start', tables: table_count, records: record_count)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.data_complete(table_count, record_count)
|
|
64
|
+
emit('data_complete', tables: table_count, records: record_count)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Table-level events
|
|
68
|
+
def self.table_start(table_name, record_count, workers: 1)
|
|
69
|
+
clear_throttle(table_name) # Reset throttle for new table
|
|
70
|
+
emit('table_start', table: table_name, records: record_count, workers: workers)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.table_progress(table_name, current, total)
|
|
74
|
+
# Throttle progress events to avoid spam
|
|
75
|
+
return unless should_emit_progress?(table_name)
|
|
76
|
+
|
|
77
|
+
percentage = total > 0 ? ((current.to_f / total) * 100).round(1) : 0
|
|
78
|
+
emit('table_progress', table: table_name, current: current, total: total, percentage: percentage)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.table_complete(table_name, record_count)
|
|
82
|
+
clear_throttle(table_name) # Clean up throttle state
|
|
83
|
+
emit('table_complete', table: table_name, records: record_count)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Index events
|
|
87
|
+
def self.indexes_start(table_count)
|
|
88
|
+
emit('indexes_start', tables: table_count)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def self.indexes_complete(table_count)
|
|
92
|
+
emit('indexes_complete', tables: table_count)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Sequence events
|
|
96
|
+
def self.sequences_start
|
|
97
|
+
emit('sequences_start')
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def self.sequences_complete
|
|
101
|
+
emit('sequences_complete')
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Error events
|
|
105
|
+
def self.error(message, context = {})
|
|
106
|
+
emit('error', { message: message }.merge(context))
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/tapsoob/version.rb
CHANGED
data/lib/tasks/tapsoob.rake
CHANGED
|
@@ -19,7 +19,7 @@ namespace :tapsoob do
|
|
|
19
19
|
FileUtils.mkpath "#{dump_path}/indexes"
|
|
20
20
|
|
|
21
21
|
# Run operation
|
|
22
|
-
Tapsoob::Operation.factory(:pull, database_uri, dump_path, opts).run
|
|
22
|
+
Tapsoob::Operation::Base.factory(:pull, database_uri, dump_path, opts).run
|
|
23
23
|
|
|
24
24
|
# Invoke cleanup task
|
|
25
25
|
Rake::Task["tapsoob:clean"].reenable
|
|
@@ -53,7 +53,7 @@ namespace :tapsoob do
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
# Run operation
|
|
56
|
-
Tapsoob::Operation.factory(:push, database_uri, dump_path, opts).run
|
|
56
|
+
Tapsoob::Operation::Base.factory(:push, database_uri, dump_path, opts).run
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
desc "Cleanup old dumps"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tapsoob
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: java
|
|
6
6
|
authors:
|
|
7
7
|
- Félix Bellanger
|
|
8
8
|
- Michael Chrisco
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sequel
|
|
@@ -102,13 +102,22 @@ files:
|
|
|
102
102
|
- lib/tapsoob/cli/schema.rb
|
|
103
103
|
- lib/tapsoob/config.rb
|
|
104
104
|
- lib/tapsoob/data_stream.rb
|
|
105
|
+
- lib/tapsoob/data_stream/base.rb
|
|
106
|
+
- lib/tapsoob/data_stream/file_partition.rb
|
|
107
|
+
- lib/tapsoob/data_stream/interleaved.rb
|
|
108
|
+
- lib/tapsoob/data_stream/keyed.rb
|
|
109
|
+
- lib/tapsoob/data_stream/keyed_partition.rb
|
|
105
110
|
- lib/tapsoob/errors.rb
|
|
106
111
|
- lib/tapsoob/log.rb
|
|
107
112
|
- lib/tapsoob/operation.rb
|
|
113
|
+
- lib/tapsoob/operation/base.rb
|
|
114
|
+
- lib/tapsoob/operation/pull.rb
|
|
115
|
+
- lib/tapsoob/operation/push.rb
|
|
108
116
|
- lib/tapsoob/progress.rb
|
|
109
117
|
- lib/tapsoob/progress/bar.rb
|
|
110
118
|
- lib/tapsoob/progress/multi_bar.rb
|
|
111
119
|
- lib/tapsoob/progress/thread_safe_bar.rb
|
|
120
|
+
- lib/tapsoob/progress_event.rb
|
|
112
121
|
- lib/tapsoob/railtie.rb
|
|
113
122
|
- lib/tapsoob/schema.rb
|
|
114
123
|
- lib/tapsoob/utils.rb
|