tapsoob 0.6.0-java → 0.6.2-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 +11 -1
- data/lib/tapsoob/cli/data_stream.rb +45 -20
- data/lib/tapsoob/operation.rb +1 -2
- data/lib/tapsoob/progress/bar.rb +245 -0
- data/lib/tapsoob/progress/multi_bar.rb +158 -0
- data/lib/tapsoob/progress/thread_safe_bar.rb +90 -0
- data/lib/tapsoob/progress.rb +11 -0
- data/lib/tapsoob/version.rb +1 -1
- data/lib/tasks/tapsoob.rake +16 -4
- metadata +5 -3
- data/lib/tapsoob/multi_progress_bar.rb +0 -234
- data/lib/tapsoob/progress_bar.rb +0 -237
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a60a9f569ac31e7a9390328af8974ee95a056a3d35b510eedafb13fd9ab3331
|
|
4
|
+
data.tar.gz: 5f6a3ca22a9b95bff31adf8a9921bfc79e3768047fbe769a0c9c3adbfe72c7f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c7f62ebad5ce2449ae283a22f5ae0e63faaa84cdce808537eda8e35f60346ff782be1826c3614fd4636787b67df8e1cc78f1ce1d74103d33c78b2ff41146dbec
|
|
7
|
+
data.tar.gz: 91911b166a9bae36b504dfa2112088fc11033f36f8b100ded1e9e9d94ad409c9bfb23a7dffe5afb5ec0d6e1adcc619a7923cf774bc9856c0013e6a78b2ae16fd
|
data/README.md
CHANGED
|
@@ -36,7 +36,7 @@ You can list all available options using the command:
|
|
|
36
36
|
tapsoob push -h
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## Piping your schema/indexes/data
|
|
40
40
|
|
|
41
41
|
Due to some needs we added ways to pipe your schema/indexes/data directly from one database to another, here's an equivalent of the export/import process described above using this technique :
|
|
42
42
|
|
|
@@ -60,6 +60,16 @@ If you're using Rails, there's also two Rake tasks provided:
|
|
|
60
60
|
* `tapsoob:pull` which dumps the database into a new folder under the `db` folder
|
|
61
61
|
* `tapsoob:push` which reads the last dump you made from `tapsoob:pull` from the `db` folder
|
|
62
62
|
|
|
63
|
+
## NEW : Full parallelization support from 0.6.1 onwards
|
|
64
|
+
|
|
65
|
+
You can now dump/load a full database or data using parallelization to speed up the process at memory cost and database load like so :
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
tapsoob pull [OPTIONS] <dump_path> <database_url> -j <number_of_threads>
|
|
69
|
+
tapsoob push [OPTIONS] <dump_path> <database_url> -j <number_of_threads>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
It defaults to a single thread as per pre 0.6.1, it is also appliable to `tapsoob data pull/push` but only when dumping to files, you can't parallelize and pipe for obvious reasons, it'll fall back to a single thread with a warning if you try to do this for safety.
|
|
63
73
|
|
|
64
74
|
## Notes
|
|
65
75
|
|
|
@@ -14,10 +14,20 @@ module Tapsoob
|
|
|
14
14
|
option :chunksize, desc: "Initial chunksize", default: 1000, type: :numeric, aliases: "-c"
|
|
15
15
|
option :tables, desc: "Shortcut to filter on a list of tables", type: :array, aliases: "-t"
|
|
16
16
|
option :"exclude-tables", desc: "Shortcut to exclude a list of tables", type: :array, aliases: "-e"
|
|
17
|
+
option :parallel, desc: "Number of parallel workers for table processing (default: 1)", default: 1, type: :numeric, aliases: "-j"
|
|
17
18
|
option :progress, desc: "Show progress", default: true, type: :boolean, aliases: "-p"
|
|
18
19
|
option :debug, desc: "Enable debug messages", default: false, type: :boolean, aliases: "-d"
|
|
19
20
|
def pull(database_url, dump_path = nil)
|
|
20
|
-
|
|
21
|
+
opts = parse_opts(options)
|
|
22
|
+
|
|
23
|
+
# Force serial mode when outputting to STDOUT (for piping)
|
|
24
|
+
# Parallel mode would interleave output and corrupt the JSON stream
|
|
25
|
+
if dump_path.nil? && opts[:parallel] && opts[:parallel] > 1
|
|
26
|
+
STDERR.puts "Warning: Parallel mode disabled when outputting to STDOUT (for piping)"
|
|
27
|
+
opts[:parallel] = 1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
op = Tapsoob::Operation.factory(:pull, database_url, dump_path, opts)
|
|
21
31
|
op.pull_data
|
|
22
32
|
end
|
|
23
33
|
|
|
@@ -25,36 +35,48 @@ module Tapsoob
|
|
|
25
35
|
option :chunksize, desc: "Initial chunksize", default: 1000, type: :numeric, aliases: "-c"
|
|
26
36
|
option :tables, desc: "Shortcut to filter on a list of tables", type: :array, aliases: "-t"
|
|
27
37
|
option :"exclude-tables", desc: "Shortcut to exclude a list of tables", type: :array, aliases: "-e"
|
|
38
|
+
option :parallel, desc: "Number of parallel workers for table processing (default: 1)", default: 1, type: :numeric, aliases: "-j"
|
|
28
39
|
option :progress, desc: "Show progress", default: true, type: :boolean, aliases: "-p"
|
|
29
40
|
option :purge, desc: "Purge data in tables prior to performing the import", default: false, type: :boolean
|
|
30
41
|
option :"skip-duplicates", desc: "Remove duplicates when loading data", default: false, type: :boolean
|
|
31
42
|
option :"discard-identity", desc: "Remove identity when pushing data (may result in creating duplicates)", default: false, type: :boolean
|
|
32
43
|
option :debug, desc: "Enable debug messages", default: false, type: :boolean, aliases: "-d"
|
|
33
44
|
def push(database_url, dump_path = nil)
|
|
34
|
-
# instantiate stuff
|
|
35
|
-
data = []
|
|
36
45
|
opts = parse_opts(options)
|
|
37
46
|
|
|
38
|
-
#
|
|
47
|
+
# If dump_path is provided, use the Operation class for proper parallel support
|
|
39
48
|
if dump_path && Dir.exist?(dump_path)
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
op = Tapsoob::Operation.factory(:push, database_url, dump_path, opts)
|
|
50
|
+
op.push_data
|
|
42
51
|
else
|
|
52
|
+
# STDIN mode: read and import data directly (no parallel support for STDIN)
|
|
53
|
+
if opts[:parallel] && opts[:parallel] > 1
|
|
54
|
+
STDERR.puts "Warning: Parallel mode not supported when reading from STDIN"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
data = []
|
|
43
58
|
STDIN.each_line { |line| data << JSON.parse(line, symbolize_names: true) }
|
|
44
|
-
end
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
# import data
|
|
61
|
+
data.each do |table|
|
|
62
|
+
table_name = table[:table_name]
|
|
63
|
+
|
|
64
|
+
# Truncate table if purge option is enabled
|
|
65
|
+
if opts[:purge]
|
|
66
|
+
db(database_url, opts)[table_name.to_sym].truncate
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
stream = Tapsoob::DataStream.factory(db(database_url, opts), {
|
|
70
|
+
table_name: table_name,
|
|
71
|
+
chunksize: opts[:default_chunksize]
|
|
72
|
+
}, { :"discard-identity" => opts[:"discard-identity"] || false, :purge => opts[:purge] || false, :debug => opts[:debug] })
|
|
73
|
+
|
|
74
|
+
begin
|
|
75
|
+
stream.import_rows(table)
|
|
76
|
+
rescue Exception => e
|
|
77
|
+
stream.log.debug e.message
|
|
78
|
+
STDERR.puts "Error loading data in #{table_name} : #{e.message}"
|
|
79
|
+
end
|
|
58
80
|
end
|
|
59
81
|
end
|
|
60
82
|
end
|
|
@@ -65,6 +87,7 @@ module Tapsoob
|
|
|
65
87
|
opts = {
|
|
66
88
|
progress: options[:progress],
|
|
67
89
|
tables: options[:tables],
|
|
90
|
+
parallel: options[:parallel],
|
|
68
91
|
debug: options[:debug]
|
|
69
92
|
}
|
|
70
93
|
|
|
@@ -85,7 +108,9 @@ module Tapsoob
|
|
|
85
108
|
end
|
|
86
109
|
|
|
87
110
|
def db(database_url, opts = {})
|
|
88
|
-
|
|
111
|
+
# Support connection pooling for parallel operations
|
|
112
|
+
parallel_workers = opts[:parallel] || 1
|
|
113
|
+
@db ||= Sequel.connect(database_url, max_connections: parallel_workers * 2)
|
|
89
114
|
@db.loggers << Tapsoob.log if opts[:debug]
|
|
90
115
|
|
|
91
116
|
# Set parameters
|
data/lib/tapsoob/operation.rb
CHANGED
|
@@ -0,0 +1,245 @@
|
|
|
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
|
|
242
|
+
|
|
243
|
+
# Backward compatibility aliases
|
|
244
|
+
ProgressBar = Tapsoob::Progress::Bar
|
|
245
|
+
ReversedProgressBar = Tapsoob::Progress::ReversedBar
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
|
|
3
|
+
module Tapsoob
|
|
4
|
+
module Progress
|
|
5
|
+
# MultiBar manages multiple progress bars in parallel
|
|
6
|
+
# Each bar gets its own line in the terminal
|
|
7
|
+
class MultiBar
|
|
8
|
+
def initialize(max_bars = 4)
|
|
9
|
+
@max_bars = max_bars
|
|
10
|
+
@bars = []
|
|
11
|
+
@mutex = Mutex.new
|
|
12
|
+
@active = true
|
|
13
|
+
@out = STDOUT
|
|
14
|
+
@last_update = Time.now
|
|
15
|
+
@reserved_lines = 0 # Track how many lines we've actually reserved
|
|
16
|
+
@max_title_width = 14 # Minimum width, will grow with longer titles
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Create a new progress bar and return it
|
|
20
|
+
def create_bar(title, total)
|
|
21
|
+
@mutex.synchronize do
|
|
22
|
+
# Remove any existing bar with the same title to prevent duplicates
|
|
23
|
+
@bars.reject! { |b| b.title == title }
|
|
24
|
+
|
|
25
|
+
# Update max title width to accommodate longer titles
|
|
26
|
+
@max_title_width = [@max_title_width, title.length].max
|
|
27
|
+
|
|
28
|
+
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
|
+
@bars << bar
|
|
39
|
+
bar
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get the current maximum title width for alignment
|
|
44
|
+
# Note: Always called from within synchronized methods, so no mutex needed
|
|
45
|
+
def max_title_width
|
|
46
|
+
@max_title_width
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Called by individual bars when they update
|
|
50
|
+
def update
|
|
51
|
+
@mutex.synchronize do
|
|
52
|
+
return unless @active
|
|
53
|
+
return unless should_redraw?
|
|
54
|
+
|
|
55
|
+
@last_update = Time.now
|
|
56
|
+
redraw_all
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Finish a specific bar - mark it as completed
|
|
61
|
+
def finish_bar(bar)
|
|
62
|
+
@mutex.synchronize do
|
|
63
|
+
return unless @active
|
|
64
|
+
|
|
65
|
+
bar.mark_finished
|
|
66
|
+
|
|
67
|
+
# Respect throttle when finishing to avoid spamming redraws
|
|
68
|
+
if should_redraw?
|
|
69
|
+
@last_update = Time.now
|
|
70
|
+
redraw_all
|
|
71
|
+
end
|
|
72
|
+
# If throttled, the next regular update will show the finished state
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Stop all progress bars and keep them visible
|
|
77
|
+
def stop
|
|
78
|
+
@mutex.synchronize do
|
|
79
|
+
@active = false
|
|
80
|
+
|
|
81
|
+
# Final cleanup: remove any duplicate titles (keep the last occurrence of each unique title)
|
|
82
|
+
@bars = @bars.reverse.uniq { |bar| bar.title }.reverse
|
|
83
|
+
|
|
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
|
+
@out.flush
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
# Check if enough time has passed to redraw (throttle to 10 updates/sec)
|
|
95
|
+
def should_redraw?
|
|
96
|
+
Time.now - @last_update >= 0.1
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def redraw_all(force = false)
|
|
100
|
+
return unless force || @active
|
|
101
|
+
return if @bars.empty?
|
|
102
|
+
|
|
103
|
+
if force && !@active
|
|
104
|
+
render_final_display
|
|
105
|
+
else
|
|
106
|
+
render_active_display
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
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
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Normal operation: show active bars + recent finished in reserved space
|
|
129
|
+
def render_active_display
|
|
130
|
+
return if @reserved_lines == 0
|
|
131
|
+
|
|
132
|
+
# Partition bars in a single pass for efficiency
|
|
133
|
+
active_bars, finished_bars = @bars.partition { |b| !b.finished? }
|
|
134
|
+
|
|
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)
|
|
139
|
+
|
|
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|
|
|
146
|
+
@out.print "\r\e[K"
|
|
147
|
+
bars_to_draw[i].render_to(@out) if i < bars_to_draw.length
|
|
148
|
+
@out.print "\n"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
@out.flush
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Backward compatibility alias
|
|
158
|
+
MultiProgressBar = Tapsoob::Progress::MultiBar
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
|
88
|
+
|
|
89
|
+
# Backward compatibility alias
|
|
90
|
+
ThreadSafeProgressBar = Tapsoob::Progress::ThreadSafeBar
|
|
@@ -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'
|
data/lib/tapsoob/version.rb
CHANGED
data/lib/tasks/tapsoob.rake
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
namespace :tapsoob do
|
|
2
|
-
desc "Pulls a database to your filesystem"
|
|
2
|
+
desc "Pulls a database to your filesystem (PARALLEL=4 for 4 workers)"
|
|
3
3
|
task :pull => :environment do
|
|
4
4
|
# Default options
|
|
5
|
-
opts={:default_chunksize => 1000, :debug => false, :resume_filename => nil, :disable_compression => false, :schema => true, :data => true, :indexes_first => false, :progress => true}
|
|
5
|
+
opts={:default_chunksize => 1000, :debug => false, :resume_filename => nil, :disable_compression => false, :schema => true, :data => true, :indexes_first => false, :progress => true, :parallel => 1}
|
|
6
|
+
|
|
7
|
+
# Allow overriding parallel workers via PARALLEL environment variable
|
|
8
|
+
if ENV['PARALLEL'] && ENV['PARALLEL'].to_i > 0
|
|
9
|
+
opts[:parallel] = ENV['PARALLEL'].to_i
|
|
10
|
+
puts "Using #{opts[:parallel]} parallel workers"
|
|
11
|
+
end
|
|
6
12
|
|
|
7
13
|
# Get the dump_path
|
|
8
14
|
dump_path = File.expand_path(Rails.root.join("db", Time.now.strftime("%Y%m%d%I%M%S%p"))).to_s
|
|
@@ -20,10 +26,16 @@ namespace :tapsoob do
|
|
|
20
26
|
Rake::Task["tapsoob:clean"].invoke
|
|
21
27
|
end
|
|
22
28
|
|
|
23
|
-
desc "Push a compatible dump on your filesystem to a database"
|
|
29
|
+
desc "Push a compatible dump on your filesystem to a database (PARALLEL=4 for 4 workers)"
|
|
24
30
|
task :push, [:timestamp] => :environment do |t, args|
|
|
25
31
|
# Default options
|
|
26
|
-
opts={:default_chunksize => 1000, :debug => false, :resume_filename => nil, :disable_compression => false, :schema => true, :data => true, :indexes_first => false, :progress => true}
|
|
32
|
+
opts={:default_chunksize => 1000, :debug => false, :resume_filename => nil, :disable_compression => false, :schema => true, :data => true, :indexes_first => false, :progress => true, :parallel => 1}
|
|
33
|
+
|
|
34
|
+
# Allow overriding parallel workers via PARALLEL environment variable
|
|
35
|
+
if ENV['PARALLEL'] && ENV['PARALLEL'].to_i > 0
|
|
36
|
+
opts[:parallel] = ENV['PARALLEL'].to_i
|
|
37
|
+
puts "Using #{opts[:parallel]} parallel workers"
|
|
38
|
+
end
|
|
27
39
|
|
|
28
40
|
# Get the dumps
|
|
29
41
|
dumps = Dir[Rails.root.join("db", "*/")].select { |e| e =~ /([0-9]{14})([A-Z]{2})/ }.sort
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tapsoob
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.2
|
|
5
5
|
platform: java
|
|
6
6
|
authors:
|
|
7
7
|
- Félix Bellanger
|
|
@@ -104,9 +104,11 @@ files:
|
|
|
104
104
|
- lib/tapsoob/data_stream.rb
|
|
105
105
|
- lib/tapsoob/errors.rb
|
|
106
106
|
- lib/tapsoob/log.rb
|
|
107
|
-
- lib/tapsoob/multi_progress_bar.rb
|
|
108
107
|
- lib/tapsoob/operation.rb
|
|
109
|
-
- lib/tapsoob/
|
|
108
|
+
- lib/tapsoob/progress.rb
|
|
109
|
+
- lib/tapsoob/progress/bar.rb
|
|
110
|
+
- lib/tapsoob/progress/multi_bar.rb
|
|
111
|
+
- lib/tapsoob/progress/thread_safe_bar.rb
|
|
110
112
|
- lib/tapsoob/railtie.rb
|
|
111
113
|
- lib/tapsoob/schema.rb
|
|
112
114
|
- lib/tapsoob/utils.rb
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
# -*- encoding : utf-8 -*-
|
|
2
|
-
require 'tapsoob/progress_bar'
|
|
3
|
-
|
|
4
|
-
# MultiProgressBar manages multiple progress bars in parallel
|
|
5
|
-
# Each bar gets its own line in the terminal
|
|
6
|
-
class MultiProgressBar
|
|
7
|
-
def initialize(max_bars = 4)
|
|
8
|
-
@max_bars = max_bars
|
|
9
|
-
@bars = []
|
|
10
|
-
@mutex = Mutex.new
|
|
11
|
-
@active = true
|
|
12
|
-
@out = STDOUT
|
|
13
|
-
@last_update = Time.now
|
|
14
|
-
@reserved_lines = 0 # Track how many lines we've actually reserved
|
|
15
|
-
@max_title_width = 14 # Minimum width, will grow with longer titles
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Create a new progress bar and return it
|
|
19
|
-
def create_bar(title, total)
|
|
20
|
-
@mutex.synchronize do
|
|
21
|
-
# Remove any existing bar with the same title to prevent duplicates
|
|
22
|
-
@bars.reject! { |b| b.title == title }
|
|
23
|
-
|
|
24
|
-
# Update max title width to accommodate longer titles
|
|
25
|
-
@max_title_width = [@max_title_width, title.length].max
|
|
26
|
-
|
|
27
|
-
bar = ThreadSafeProgressBar.new(title, total, self)
|
|
28
|
-
|
|
29
|
-
# Reserve a line for this new bar during active updates
|
|
30
|
-
# Cap at 2 * max_bars to show active workers + some recent finished bars
|
|
31
|
-
if @reserved_lines < @max_bars * 2
|
|
32
|
-
@out.print "\n"
|
|
33
|
-
@out.flush
|
|
34
|
-
@reserved_lines += 1
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
@bars << bar
|
|
38
|
-
bar
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Get the current maximum title width for alignment
|
|
43
|
-
# Note: Always called from within synchronized methods, so no mutex needed
|
|
44
|
-
def max_title_width
|
|
45
|
-
@max_title_width
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Called by individual bars when they update
|
|
49
|
-
def update
|
|
50
|
-
@mutex.synchronize do
|
|
51
|
-
return unless @active
|
|
52
|
-
return unless should_redraw?
|
|
53
|
-
|
|
54
|
-
@last_update = Time.now
|
|
55
|
-
redraw_all
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Finish a specific bar - mark it as completed
|
|
60
|
-
def finish_bar(bar)
|
|
61
|
-
@mutex.synchronize do
|
|
62
|
-
return unless @active
|
|
63
|
-
|
|
64
|
-
bar.mark_finished
|
|
65
|
-
|
|
66
|
-
# Respect throttle when finishing to avoid spamming redraws
|
|
67
|
-
if should_redraw?
|
|
68
|
-
@last_update = Time.now
|
|
69
|
-
redraw_all
|
|
70
|
-
end
|
|
71
|
-
# If throttled, the next regular update will show the finished state
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Stop all progress bars and keep them visible
|
|
76
|
-
def stop
|
|
77
|
-
@mutex.synchronize do
|
|
78
|
-
@active = false
|
|
79
|
-
|
|
80
|
-
# Final cleanup: remove any duplicate titles (keep the last occurrence of each unique title)
|
|
81
|
-
@bars = @bars.reverse.uniq { |bar| bar.title }.reverse
|
|
82
|
-
|
|
83
|
-
# Final redraw to show completed state (skip active check)
|
|
84
|
-
redraw_all(true)
|
|
85
|
-
# Move cursor past all bars
|
|
86
|
-
@out.print "\n"
|
|
87
|
-
@out.flush
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
private
|
|
92
|
-
|
|
93
|
-
# Check if enough time has passed to redraw (throttle to 10 updates/sec)
|
|
94
|
-
def should_redraw?
|
|
95
|
-
Time.now - @last_update >= 0.1
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def redraw_all(force = false)
|
|
99
|
-
return unless force || @active
|
|
100
|
-
return if @bars.empty?
|
|
101
|
-
|
|
102
|
-
if force && !@active
|
|
103
|
-
render_final_display
|
|
104
|
-
else
|
|
105
|
-
render_active_display
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Final display: show all completed bars
|
|
110
|
-
def render_final_display
|
|
111
|
-
# Clear the reserved lines first
|
|
112
|
-
if @reserved_lines > 0
|
|
113
|
-
@out.print "\r\e[#{@reserved_lines}A"
|
|
114
|
-
@reserved_lines.times { @out.print "\r\e[K\n" }
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Print all bars (adds new lines as needed)
|
|
118
|
-
@bars.each do |bar|
|
|
119
|
-
@out.print "\r\e[K"
|
|
120
|
-
bar.render_to(@out)
|
|
121
|
-
@out.print "\n"
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
@out.flush
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Normal operation: show active bars + recent finished in reserved space
|
|
128
|
-
def render_active_display
|
|
129
|
-
return if @reserved_lines == 0
|
|
130
|
-
|
|
131
|
-
# Partition bars in a single pass for efficiency
|
|
132
|
-
active_bars, finished_bars = @bars.partition { |b| !b.finished? }
|
|
133
|
-
|
|
134
|
-
# Build display: active bars first, then recent finished to fill remaining space
|
|
135
|
-
# Ensure we don't request negative count from .last()
|
|
136
|
-
remaining_space = [@reserved_lines - active_bars.length, 0].max
|
|
137
|
-
bars_to_draw = active_bars + finished_bars.last(remaining_space)
|
|
138
|
-
|
|
139
|
-
# If we have more bars than reserved lines, show only the most recent
|
|
140
|
-
bars_to_draw = bars_to_draw.last(@reserved_lines) if bars_to_draw.length > @reserved_lines
|
|
141
|
-
|
|
142
|
-
# Move up and redraw in reserved space
|
|
143
|
-
@out.print "\r\e[#{@reserved_lines}A"
|
|
144
|
-
@reserved_lines.times do |i|
|
|
145
|
-
@out.print "\r\e[K"
|
|
146
|
-
bars_to_draw[i].render_to(@out) if i < bars_to_draw.length
|
|
147
|
-
@out.print "\n"
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
@out.flush
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# Thread-safe progress bar that reports to a MultiProgressBar
|
|
155
|
-
class ThreadSafeProgressBar < ProgressBar
|
|
156
|
-
attr_reader :title
|
|
157
|
-
|
|
158
|
-
def initialize(title, total, multi_progress_bar)
|
|
159
|
-
@multi_progress_bar = multi_progress_bar
|
|
160
|
-
@out = STDOUT # Need this for get_width to work
|
|
161
|
-
# Don't call parent initialize, we'll manage output ourselves
|
|
162
|
-
@title = title
|
|
163
|
-
@total = total
|
|
164
|
-
@terminal_width = 80
|
|
165
|
-
@bar_mark = "="
|
|
166
|
-
@current = 0
|
|
167
|
-
@previous = 0
|
|
168
|
-
@finished_p = false
|
|
169
|
-
@start_time = ::Time.now
|
|
170
|
-
@previous_time = @start_time
|
|
171
|
-
@format_arguments = [:title, :percentage, :bar, :stat]
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# Override show to notify multi-progress instead of direct output
|
|
175
|
-
def show
|
|
176
|
-
@previous_time = ::Time.now # Update to prevent time-based refresh spam
|
|
177
|
-
@multi_progress_bar.update
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Render this bar to the given output stream
|
|
181
|
-
def render_to(out)
|
|
182
|
-
# Get dynamic title width from MultiProgressBar for consistent alignment
|
|
183
|
-
# Store as instance variable so parent class fmt_* methods can use it
|
|
184
|
-
@title_width = @multi_progress_bar.max_title_width
|
|
185
|
-
|
|
186
|
-
# Recalculate terminal width to handle resizes and use full width
|
|
187
|
-
width = get_width
|
|
188
|
-
# Calculate bar width: total_width - fixed_elements - padding
|
|
189
|
-
# Fixed: title(variable) + " "(1) + percentage(4) + " "(1) + "|"(1) + "|"(1) + " "(1) + timer(15) = title_width + 25
|
|
190
|
-
# Padding: +3 for timer fluctuations and safety
|
|
191
|
-
fixed_chars = @title_width + 28
|
|
192
|
-
@terminal_width = [width - fixed_chars, 20].max
|
|
193
|
-
|
|
194
|
-
# Build format string with dynamic title width
|
|
195
|
-
format = "%-#{@title_width}s %3d%% %s %s"
|
|
196
|
-
arguments = @format_arguments.map { |method| send("fmt_#{method}") }
|
|
197
|
-
line = sprintf(format, *arguments)
|
|
198
|
-
|
|
199
|
-
# Ensure line doesn't exceed terminal width to prevent wrapping
|
|
200
|
-
# Leave 2 chars margin for safety
|
|
201
|
-
line = line[0, width - 2] if line.length > width - 2
|
|
202
|
-
|
|
203
|
-
out.print(line)
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# Override clear to do nothing (managed by MultiProgressBar)
|
|
207
|
-
def clear
|
|
208
|
-
# no-op
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# Mark this bar as finished (for tracking)
|
|
212
|
-
def mark_finished
|
|
213
|
-
@finished_p = true
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
# Override to use the same @finished_p flag
|
|
217
|
-
def finished?
|
|
218
|
-
@finished_p
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
# Override finish to notify multi-progress
|
|
222
|
-
def finish
|
|
223
|
-
@current = @total
|
|
224
|
-
@multi_progress_bar.finish_bar(self)
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
# Override inc to check if we need to update
|
|
228
|
-
def inc(step = 1)
|
|
229
|
-
@current += step
|
|
230
|
-
@current = @total if @current > @total
|
|
231
|
-
show_if_needed
|
|
232
|
-
@previous = @current
|
|
233
|
-
end
|
|
234
|
-
end
|
data/lib/tapsoob/progress_bar.rb
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
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
|
-
class ProgressBar
|
|
14
|
-
VERSION = "0.9"
|
|
15
|
-
|
|
16
|
-
def initialize (title, total, out = STDOUT, title_width = nil)
|
|
17
|
-
@title = title
|
|
18
|
-
@total = total
|
|
19
|
-
@out = out
|
|
20
|
-
@terminal_width = 80
|
|
21
|
-
@bar_mark = "="
|
|
22
|
-
@current = 0
|
|
23
|
-
@previous = 0
|
|
24
|
-
@finished_p = false
|
|
25
|
-
@start_time = ::Time.now
|
|
26
|
-
@previous_time = @start_time
|
|
27
|
-
# Set title width: use provided width, or accommodate the title, with a minimum of 14
|
|
28
|
-
@title_width = title_width || [title.length, 14].max
|
|
29
|
-
@format = "%-#{@title_width}s %3d%% %s %s"
|
|
30
|
-
@format_arguments = [:title, :percentage, :bar, :stat]
|
|
31
|
-
clear
|
|
32
|
-
show
|
|
33
|
-
end
|
|
34
|
-
attr_reader :title
|
|
35
|
-
attr_reader :current
|
|
36
|
-
attr_reader :total
|
|
37
|
-
attr_accessor :start_time
|
|
38
|
-
|
|
39
|
-
private
|
|
40
|
-
def fmt_bar
|
|
41
|
-
bar_width = do_percentage * @terminal_width / 100
|
|
42
|
-
sprintf("|%s%s|",
|
|
43
|
-
@bar_mark * bar_width,
|
|
44
|
-
" " * (@terminal_width - bar_width))
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def fmt_percentage
|
|
48
|
-
do_percentage
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def fmt_stat
|
|
52
|
-
if @finished_p then elapsed else eta end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def fmt_stat_for_file_transfer
|
|
56
|
-
if @finished_p then
|
|
57
|
-
sprintf("%s %s %s", bytes, transfer_rate, elapsed)
|
|
58
|
-
else
|
|
59
|
-
sprintf("%s %s %s", bytes, transfer_rate, eta)
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def fmt_title
|
|
64
|
-
@title[0,(@title_width - 1)] + ":"
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def convert_bytes (bytes)
|
|
68
|
-
if bytes < 1024
|
|
69
|
-
sprintf("%6dB", bytes)
|
|
70
|
-
elsif bytes < 1024 * 1000 # 1000kb
|
|
71
|
-
sprintf("%5.1fKB", bytes.to_f / 1024)
|
|
72
|
-
elsif bytes < 1024 * 1024 * 1000 # 1000mb
|
|
73
|
-
sprintf("%5.1fMB", bytes.to_f / 1024 / 1024)
|
|
74
|
-
else
|
|
75
|
-
sprintf("%5.1fGB", bytes.to_f / 1024 / 1024 / 1024)
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def transfer_rate
|
|
80
|
-
bytes_per_second = @current.to_f / (::Time.now - @start_time)
|
|
81
|
-
sprintf("%s/s", convert_bytes(bytes_per_second))
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def bytes
|
|
85
|
-
convert_bytes(@current)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def format_time (t)
|
|
89
|
-
t = t.to_i
|
|
90
|
-
sec = t % 60
|
|
91
|
-
min = (t / 60) % 60
|
|
92
|
-
hour = t / 3600
|
|
93
|
-
sprintf("%02d:%02d:%02d", hour, min, sec);
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# ETA stands for Estimated Time of Arrival.
|
|
97
|
-
def eta
|
|
98
|
-
if @current == 0
|
|
99
|
-
"ETA: --:--:--"
|
|
100
|
-
else
|
|
101
|
-
elapsed = ::Time.now - @start_time
|
|
102
|
-
eta = elapsed * @total / @current - elapsed;
|
|
103
|
-
sprintf("ETA: %s", format_time(eta))
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def elapsed
|
|
108
|
-
elapsed = ::Time.now - @start_time
|
|
109
|
-
sprintf("Time: %s", format_time(elapsed))
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def eol
|
|
113
|
-
if @finished_p then "\n" else "\r" end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def do_percentage
|
|
117
|
-
if @total.zero?
|
|
118
|
-
100
|
|
119
|
-
else
|
|
120
|
-
@current * 100 / @total
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def get_width
|
|
125
|
-
# FIXME: I don't know how portable it is.
|
|
126
|
-
default_width = 80
|
|
127
|
-
begin
|
|
128
|
-
tiocgwinsz = 0x5413
|
|
129
|
-
data = [0, 0, 0, 0].pack("SSSS")
|
|
130
|
-
if @out.ioctl(tiocgwinsz, data) >= 0 then
|
|
131
|
-
rows, cols, xpixels, ypixels = data.unpack("SSSS")
|
|
132
|
-
if cols > 0 then cols else default_width end
|
|
133
|
-
else
|
|
134
|
-
default_width
|
|
135
|
-
end
|
|
136
|
-
rescue Exception
|
|
137
|
-
default_width
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def show
|
|
142
|
-
arguments = @format_arguments.map {|method|
|
|
143
|
-
method = sprintf("fmt_%s", method)
|
|
144
|
-
send(method)
|
|
145
|
-
}
|
|
146
|
-
line = sprintf(@format, *arguments)
|
|
147
|
-
|
|
148
|
-
width = get_width
|
|
149
|
-
if line.length == width - 1
|
|
150
|
-
@out.print(line + eol)
|
|
151
|
-
@out.flush
|
|
152
|
-
elsif line.length >= width
|
|
153
|
-
@terminal_width = [@terminal_width - (line.length - width + 1), 0].max
|
|
154
|
-
if @terminal_width == 0 then @out.print(line + eol) else show end
|
|
155
|
-
else # line.length < width - 1
|
|
156
|
-
@terminal_width += width - line.length + 1
|
|
157
|
-
show
|
|
158
|
-
end
|
|
159
|
-
@previous_time = ::Time.now
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def show_if_needed
|
|
163
|
-
if @total.zero?
|
|
164
|
-
cur_percentage = 100
|
|
165
|
-
prev_percentage = 0
|
|
166
|
-
else
|
|
167
|
-
cur_percentage = (@current * 100 / @total).to_i
|
|
168
|
-
prev_percentage = (@previous * 100 / @total).to_i
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
# Use "!=" instead of ">" to support negative changes
|
|
172
|
-
if cur_percentage != prev_percentage ||
|
|
173
|
-
::Time.now - @previous_time >= 1 || @finished_p
|
|
174
|
-
show
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
public
|
|
179
|
-
def clear
|
|
180
|
-
@out.print "\r"
|
|
181
|
-
@out.print(" " * (get_width - 1))
|
|
182
|
-
@out.print "\r"
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def finish
|
|
186
|
-
@current = @total
|
|
187
|
-
@finished_p = true
|
|
188
|
-
show
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def finished?
|
|
192
|
-
@finished_p
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def file_transfer_mode
|
|
196
|
-
@format_arguments = [:title, :percentage, :bar, :stat_for_file_transfer]
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def format= (format)
|
|
200
|
-
@format = format
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def format_arguments= (arguments)
|
|
204
|
-
@format_arguments = arguments
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def halt
|
|
208
|
-
@finished_p = true
|
|
209
|
-
show
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def inc (step = 1)
|
|
213
|
-
@current += step
|
|
214
|
-
@current = @total if @current > @total
|
|
215
|
-
show_if_needed
|
|
216
|
-
@previous = @current
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
def set (count)
|
|
220
|
-
if count < 0 || count > @total
|
|
221
|
-
raise "invalid count: #{count} (total: #{@total})"
|
|
222
|
-
end
|
|
223
|
-
@current = count
|
|
224
|
-
show_if_needed
|
|
225
|
-
@previous = @current
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
def inspect
|
|
229
|
-
"#<ProgressBar:#{@current}/#{@total}>"
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
class ReversedProgressBar < ProgressBar
|
|
234
|
-
def do_percentage
|
|
235
|
-
100 - super
|
|
236
|
-
end
|
|
237
|
-
end
|