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.
@@ -2,8 +2,10 @@
2
2
 
3
3
  module Tapsoob
4
4
  module Progress
5
- # MultiBar manages multiple progress bars in parallel
6
- # Each bar gets its own line in the terminal
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 keep them visible
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
- # Final cleanup: remove any duplicate titles (keep the last occurrence of each unique title)
82
- @bars = @bars.reverse.uniq { |bar| bar.title }.reverse
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 force || @active
130
+ return unless @active
101
131
  return if @bars.empty?
102
132
 
103
- if force && !@active
104
- render_final_display
105
- else
106
- render_active_display
107
- end
133
+ render_active_display
108
134
  end
109
135
 
110
- # Final display: show all completed bars
111
- def render_final_display
112
- # Clear the reserved lines first
113
- if @reserved_lines > 0
114
- @out.print "\r\e[#{@reserved_lines}A"
115
- @reserved_lines.times { @out.print "\r\e[K\n" }
116
- end
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
- # Normal operation: show active bars + recent finished in reserved space
145
+ # Render the complete display: progress bars + separator + info line
129
146
  def render_active_display
130
- return if @reserved_lines == 0
147
+ return if @total_lines == 0
131
148
 
132
- # Partition bars in a single pass for efficiency
133
- active_bars, finished_bars = @bars.partition { |b| !b.finished? }
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
- # Build display: active bars first, then recent finished to fill remaining space
136
- # Ensure we don't request negative count from .last()
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
- # If we have more bars than reserved lines, show only the most recent
141
- bars_to_draw = bars_to_draw.last(@reserved_lines) if bars_to_draw.length > @reserved_lines
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
@@ -85,6 +85,3 @@ module Tapsoob
85
85
  end
86
86
  end
87
87
  end
88
-
89
- # Backward compatibility alias
90
- ThreadSafeProgressBar = Tapsoob::Progress::ThreadSafeBar
@@ -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
@@ -1,4 +1,4 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module Tapsoob
3
- VERSION = "0.6.2".freeze
3
+ VERSION = "0.7.0".freeze
4
4
  end
@@ -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.6.2
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-29 00:00:00.000000000 Z
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