tapsoob 0.6.1-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 -665
- data/lib/tapsoob/progress/bar.rb +241 -0
- data/lib/tapsoob/progress/multi_bar.rb +190 -0
- data/lib/tapsoob/progress/thread_safe_bar.rb +87 -0
- data/lib/tapsoob/progress.rb +11 -0
- data/lib/tapsoob/progress_event.rb +109 -0
- data/lib/tapsoob/version.rb +1 -1
- data/lib/tasks/tapsoob.rake +2 -2
- metadata +15 -4
- data/lib/tapsoob/multi_progress_bar.rb +0 -234
- data/lib/tapsoob/progress_bar.rb +0 -237
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Ruby/ProgressBar - a text progress bar library
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2001-2005 Satoru Takabayashi <satoru@namazu.org>
|
|
6
|
+
# All rights reserved.
|
|
7
|
+
# This is free software with ABSOLUTELY NO WARRANTY.
|
|
8
|
+
#
|
|
9
|
+
# You can redistribute it and/or modify it under the terms
|
|
10
|
+
# of Ruby's license.
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
module Tapsoob
|
|
14
|
+
module Progress
|
|
15
|
+
class Bar
|
|
16
|
+
VERSION = "0.9"
|
|
17
|
+
|
|
18
|
+
def initialize (title, total, out = STDOUT, title_width = nil)
|
|
19
|
+
@title = title
|
|
20
|
+
@total = total
|
|
21
|
+
@out = out
|
|
22
|
+
@terminal_width = 80
|
|
23
|
+
@bar_mark = "="
|
|
24
|
+
@current = 0
|
|
25
|
+
@previous = 0
|
|
26
|
+
@finished_p = false
|
|
27
|
+
@start_time = ::Time.now
|
|
28
|
+
@previous_time = @start_time
|
|
29
|
+
# Set title width: use provided width, or accommodate the title, with a minimum of 14
|
|
30
|
+
@title_width = title_width || [title.length, 14].max
|
|
31
|
+
@format = "%-#{@title_width}s %3d%% %s %s"
|
|
32
|
+
@format_arguments = [:title, :percentage, :bar, :stat]
|
|
33
|
+
clear
|
|
34
|
+
show
|
|
35
|
+
end
|
|
36
|
+
attr_reader :title
|
|
37
|
+
attr_reader :current
|
|
38
|
+
attr_reader :total
|
|
39
|
+
attr_accessor :start_time
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
def fmt_bar
|
|
43
|
+
bar_width = do_percentage * @terminal_width / 100
|
|
44
|
+
sprintf("|%s%s|",
|
|
45
|
+
@bar_mark * bar_width,
|
|
46
|
+
" " * (@terminal_width - bar_width))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def fmt_percentage
|
|
50
|
+
do_percentage
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def fmt_stat
|
|
54
|
+
if @finished_p then elapsed else eta end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def fmt_stat_for_file_transfer
|
|
58
|
+
if @finished_p then
|
|
59
|
+
sprintf("%s %s %s", bytes, transfer_rate, elapsed)
|
|
60
|
+
else
|
|
61
|
+
sprintf("%s %s %s", bytes, transfer_rate, eta)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def fmt_title
|
|
66
|
+
@title[0,(@title_width - 1)] + ":"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def convert_bytes (bytes)
|
|
70
|
+
if bytes < 1024
|
|
71
|
+
sprintf("%6dB", bytes)
|
|
72
|
+
elsif bytes < 1024 * 1000 # 1000kb
|
|
73
|
+
sprintf("%5.1fKB", bytes.to_f / 1024)
|
|
74
|
+
elsif bytes < 1024 * 1024 * 1000 # 1000mb
|
|
75
|
+
sprintf("%5.1fMB", bytes.to_f / 1024 / 1024)
|
|
76
|
+
else
|
|
77
|
+
sprintf("%5.1fGB", bytes.to_f / 1024 / 1024 / 1024)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def transfer_rate
|
|
82
|
+
bytes_per_second = @current.to_f / (::Time.now - @start_time)
|
|
83
|
+
sprintf("%s/s", convert_bytes(bytes_per_second))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def bytes
|
|
87
|
+
convert_bytes(@current)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def format_time (t)
|
|
91
|
+
t = t.to_i
|
|
92
|
+
sec = t % 60
|
|
93
|
+
min = (t / 60) % 60
|
|
94
|
+
hour = t / 3600
|
|
95
|
+
sprintf("%02d:%02d:%02d", hour, min, sec);
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# ETA stands for Estimated Time of Arrival.
|
|
99
|
+
def eta
|
|
100
|
+
if @current == 0
|
|
101
|
+
"ETA: --:--:--"
|
|
102
|
+
else
|
|
103
|
+
elapsed = ::Time.now - @start_time
|
|
104
|
+
eta = elapsed * @total / @current - elapsed;
|
|
105
|
+
sprintf("ETA: %s", format_time(eta))
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def elapsed
|
|
110
|
+
elapsed = ::Time.now - @start_time
|
|
111
|
+
sprintf("Time: %s", format_time(elapsed))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def eol
|
|
115
|
+
if @finished_p then "\n" else "\r" end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def do_percentage
|
|
119
|
+
if @total.zero?
|
|
120
|
+
100
|
|
121
|
+
else
|
|
122
|
+
@current * 100 / @total
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def get_width
|
|
127
|
+
# FIXME: I don't know how portable it is.
|
|
128
|
+
default_width = 80
|
|
129
|
+
begin
|
|
130
|
+
tiocgwinsz = 0x5413
|
|
131
|
+
data = [0, 0, 0, 0].pack("SSSS")
|
|
132
|
+
if @out.ioctl(tiocgwinsz, data) >= 0 then
|
|
133
|
+
rows, cols, xpixels, ypixels = data.unpack("SSSS")
|
|
134
|
+
if cols > 0 then cols else default_width end
|
|
135
|
+
else
|
|
136
|
+
default_width
|
|
137
|
+
end
|
|
138
|
+
rescue Exception
|
|
139
|
+
default_width
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def show
|
|
144
|
+
arguments = @format_arguments.map {|method|
|
|
145
|
+
method = sprintf("fmt_%s", method)
|
|
146
|
+
send(method)
|
|
147
|
+
}
|
|
148
|
+
line = sprintf(@format, *arguments)
|
|
149
|
+
|
|
150
|
+
width = get_width
|
|
151
|
+
if line.length == width - 1
|
|
152
|
+
@out.print(line + eol)
|
|
153
|
+
@out.flush
|
|
154
|
+
elsif line.length >= width
|
|
155
|
+
@terminal_width = [@terminal_width - (line.length - width + 1), 0].max
|
|
156
|
+
if @terminal_width == 0 then @out.print(line + eol) else show end
|
|
157
|
+
else # line.length < width - 1
|
|
158
|
+
@terminal_width += width - line.length + 1
|
|
159
|
+
show
|
|
160
|
+
end
|
|
161
|
+
@previous_time = ::Time.now
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def show_if_needed
|
|
165
|
+
if @total.zero?
|
|
166
|
+
cur_percentage = 100
|
|
167
|
+
prev_percentage = 0
|
|
168
|
+
else
|
|
169
|
+
cur_percentage = (@current * 100 / @total).to_i
|
|
170
|
+
prev_percentage = (@previous * 100 / @total).to_i
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Use "!=" instead of ">" to support negative changes
|
|
174
|
+
if cur_percentage != prev_percentage ||
|
|
175
|
+
::Time.now - @previous_time >= 1 || @finished_p
|
|
176
|
+
show
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
public
|
|
181
|
+
def clear
|
|
182
|
+
@out.print "\r"
|
|
183
|
+
@out.print(" " * (get_width - 1))
|
|
184
|
+
@out.print "\r"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def finish
|
|
188
|
+
@current = @total
|
|
189
|
+
@finished_p = true
|
|
190
|
+
show
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def finished?
|
|
194
|
+
@finished_p
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def file_transfer_mode
|
|
198
|
+
@format_arguments = [:title, :percentage, :bar, :stat_for_file_transfer]
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def format= (format)
|
|
202
|
+
@format = format
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def format_arguments= (arguments)
|
|
206
|
+
@format_arguments = arguments
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def halt
|
|
210
|
+
@finished_p = true
|
|
211
|
+
show
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def inc (step = 1)
|
|
215
|
+
@current += step
|
|
216
|
+
@current = @total if @current > @total
|
|
217
|
+
show_if_needed
|
|
218
|
+
@previous = @current
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def set (count)
|
|
222
|
+
if count < 0 || count > @total
|
|
223
|
+
raise "invalid count: #{count} (total: #{@total})"
|
|
224
|
+
end
|
|
225
|
+
@current = count
|
|
226
|
+
show_if_needed
|
|
227
|
+
@previous = @current
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def inspect
|
|
231
|
+
"#<Tapsoob::Progress::Bar:#{@current}/#{@total}>"
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
class ReversedBar < Bar
|
|
236
|
+
def do_percentage
|
|
237
|
+
100 - super
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
|
|
3
|
+
module Tapsoob
|
|
4
|
+
module Progress
|
|
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)
|
|
9
|
+
class MultiBar
|
|
10
|
+
def initialize(max_bars = 4)
|
|
11
|
+
@max_bars = max_bars
|
|
12
|
+
@bars = []
|
|
13
|
+
@mutex = Mutex.new
|
|
14
|
+
@active = true
|
|
15
|
+
@out = STDOUT
|
|
16
|
+
@last_update = Time.now
|
|
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
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Create a new progress bar and return it
|
|
34
|
+
def create_bar(title, total)
|
|
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
|
+
|
|
44
|
+
# Remove any existing bar with the same title to prevent duplicates
|
|
45
|
+
@bars.reject! { |b| b.title == title }
|
|
46
|
+
|
|
47
|
+
# Update max title width to accommodate longer titles
|
|
48
|
+
@max_title_width = [@max_title_width, title.length].max
|
|
49
|
+
|
|
50
|
+
bar = ThreadSafeBar.new(title, total, self)
|
|
51
|
+
@bars << bar
|
|
52
|
+
bar
|
|
53
|
+
end
|
|
54
|
+
end
|
|
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
|
+
|
|
65
|
+
# Get the current maximum title width for alignment
|
|
66
|
+
# Note: Always called from within synchronized methods, so no mutex needed
|
|
67
|
+
def max_title_width
|
|
68
|
+
@max_title_width
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Called by individual bars when they update
|
|
72
|
+
def update
|
|
73
|
+
@mutex.synchronize do
|
|
74
|
+
return unless @active
|
|
75
|
+
return unless should_redraw?
|
|
76
|
+
|
|
77
|
+
@last_update = Time.now
|
|
78
|
+
redraw_all
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Finish a specific bar - mark it as completed
|
|
83
|
+
def finish_bar(bar)
|
|
84
|
+
@mutex.synchronize do
|
|
85
|
+
return unless @active
|
|
86
|
+
|
|
87
|
+
bar.mark_finished
|
|
88
|
+
|
|
89
|
+
# Respect throttle when finishing to avoid spamming redraws
|
|
90
|
+
if should_redraw?
|
|
91
|
+
@last_update = Time.now
|
|
92
|
+
redraw_all
|
|
93
|
+
end
|
|
94
|
+
# If throttled, the next regular update will show the finished state
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Stop all progress bars and clear them from display
|
|
99
|
+
def stop
|
|
100
|
+
@mutex.synchronize do
|
|
101
|
+
return unless @active # Already stopped
|
|
102
|
+
@active = false
|
|
103
|
+
|
|
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
|
|
117
|
+
|
|
118
|
+
@out.flush
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
# Check if enough time has passed to redraw (throttle to 10 updates/sec)
|
|
125
|
+
def should_redraw?
|
|
126
|
+
Time.now - @last_update >= 0.1
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def redraw_all(force = false)
|
|
130
|
+
return unless @active
|
|
131
|
+
return if @bars.empty?
|
|
132
|
+
|
|
133
|
+
render_active_display
|
|
134
|
+
end
|
|
135
|
+
|
|
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)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Render the complete display: progress bars + separator + info line
|
|
146
|
+
def render_active_display
|
|
147
|
+
return if @total_lines == 0
|
|
148
|
+
|
|
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)
|
|
152
|
+
|
|
153
|
+
# Move cursor up to first line
|
|
154
|
+
@out.print "\e[#{@total_lines}A"
|
|
155
|
+
|
|
156
|
+
# Draw progress bars (they handle their own width)
|
|
157
|
+
@max_bars.times do |i|
|
|
158
|
+
@out.print "\r\e[K"
|
|
159
|
+
bars_to_draw[i].render_to(@out) if i < bars_to_draw.length
|
|
160
|
+
@out.print "\n"
|
|
161
|
+
end
|
|
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
|
+
|
|
186
|
+
@out.flush
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
|
|
3
|
+
module Tapsoob
|
|
4
|
+
module Progress
|
|
5
|
+
# Thread-safe progress bar that reports to a MultiBar
|
|
6
|
+
class ThreadSafeBar < Bar
|
|
7
|
+
attr_reader :title
|
|
8
|
+
|
|
9
|
+
def initialize(title, total, multi_progress_bar)
|
|
10
|
+
@multi_progress_bar = multi_progress_bar
|
|
11
|
+
@out = STDOUT # Need this for get_width to work
|
|
12
|
+
# Don't call parent initialize, we'll manage output ourselves
|
|
13
|
+
@title = title
|
|
14
|
+
@total = total
|
|
15
|
+
@terminal_width = 80
|
|
16
|
+
@bar_mark = "="
|
|
17
|
+
@current = 0
|
|
18
|
+
@previous = 0
|
|
19
|
+
@finished_p = false
|
|
20
|
+
@start_time = ::Time.now
|
|
21
|
+
@previous_time = @start_time
|
|
22
|
+
@format_arguments = [:title, :percentage, :bar, :stat]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Override show to notify multi-progress instead of direct output
|
|
26
|
+
def show
|
|
27
|
+
@previous_time = ::Time.now # Update to prevent time-based refresh spam
|
|
28
|
+
@multi_progress_bar.update
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Render this bar to the given output stream
|
|
32
|
+
def render_to(out)
|
|
33
|
+
# Get dynamic title width from MultiBar for consistent alignment
|
|
34
|
+
# Store as instance variable so parent class fmt_* methods can use it
|
|
35
|
+
@title_width = @multi_progress_bar.max_title_width
|
|
36
|
+
|
|
37
|
+
# Recalculate terminal width to handle resizes and use full width
|
|
38
|
+
width = get_width
|
|
39
|
+
# Calculate bar width: total_width - fixed_elements - padding
|
|
40
|
+
# Fixed: title(variable) + " "(1) + percentage(4) + " "(1) + "|"(1) + "|"(1) + " "(1) + timer(15) = title_width + 25
|
|
41
|
+
# Padding: +3 for timer fluctuations and safety
|
|
42
|
+
fixed_chars = @title_width + 28
|
|
43
|
+
@terminal_width = [width - fixed_chars, 20].max
|
|
44
|
+
|
|
45
|
+
# Build format string with dynamic title width
|
|
46
|
+
format = "%-#{@title_width}s %3d%% %s %s"
|
|
47
|
+
arguments = @format_arguments.map { |method| send("fmt_#{method}") }
|
|
48
|
+
line = sprintf(format, *arguments)
|
|
49
|
+
|
|
50
|
+
# Ensure line doesn't exceed terminal width to prevent wrapping
|
|
51
|
+
# Leave 2 chars margin for safety
|
|
52
|
+
line = line[0, width - 2] if line.length > width - 2
|
|
53
|
+
|
|
54
|
+
out.print(line)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Override clear to do nothing (managed by MultiBar)
|
|
58
|
+
def clear
|
|
59
|
+
# no-op
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Mark this bar as finished (for tracking)
|
|
63
|
+
def mark_finished
|
|
64
|
+
@finished_p = true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Override to use the same @finished_p flag
|
|
68
|
+
def finished?
|
|
69
|
+
@finished_p
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Override finish to notify multi-progress
|
|
73
|
+
def finish
|
|
74
|
+
@current = @total
|
|
75
|
+
@multi_progress_bar.finish_bar(self)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Override inc to check if we need to update
|
|
79
|
+
def inc(step = 1)
|
|
80
|
+
@current += step
|
|
81
|
+
@current = @total if @current > @total
|
|
82
|
+
show_if_needed
|
|
83
|
+
@previous = @current
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Progress bar module for Tapsoob
|
|
4
|
+
# Provides progress tracking for database operations with support for:
|
|
5
|
+
# - Single progress bars (Bar)
|
|
6
|
+
# - Multiple parallel progress bars (MultiBar)
|
|
7
|
+
# - Thread-safe progress bars (ThreadSafeBar)
|
|
8
|
+
|
|
9
|
+
require 'tapsoob/progress/bar'
|
|
10
|
+
require 'tapsoob/progress/multi_bar'
|
|
11
|
+
require 'tapsoob/progress/thread_safe_bar'
|
|
@@ -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,11 +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
|
-
- lib/tapsoob/multi_progress_bar.rb
|
|
108
112
|
- lib/tapsoob/operation.rb
|
|
109
|
-
- lib/tapsoob/
|
|
113
|
+
- lib/tapsoob/operation/base.rb
|
|
114
|
+
- lib/tapsoob/operation/pull.rb
|
|
115
|
+
- lib/tapsoob/operation/push.rb
|
|
116
|
+
- lib/tapsoob/progress.rb
|
|
117
|
+
- lib/tapsoob/progress/bar.rb
|
|
118
|
+
- lib/tapsoob/progress/multi_bar.rb
|
|
119
|
+
- lib/tapsoob/progress/thread_safe_bar.rb
|
|
120
|
+
- lib/tapsoob/progress_event.rb
|
|
110
121
|
- lib/tapsoob/railtie.rb
|
|
111
122
|
- lib/tapsoob/schema.rb
|
|
112
123
|
- lib/tapsoob/utils.rb
|