themigrator 0.1.12 → 0.1.14
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/.gitignore +1 -0
- data/Rakefile +5 -5
- data/bin/console +3 -3
- data/bin/testui +20 -0
- data/exe/themigrator +1 -6
- data/how_to_run_it +1 -0
- data/lib/themigrator/cli.rb +11 -16
- data/lib/themigrator/logger.rb +8 -10
- data/lib/themigrator/migration.rb +13 -19
- data/lib/themigrator/migration_progress.rb +69 -59
- data/lib/themigrator/migrator.rb +24 -29
- data/lib/themigrator/progress_window.rb +20 -0
- data/lib/themigrator/script.rb +29 -32
- data/lib/themigrator/script_pool.rb +18 -22
- data/lib/themigrator/ui/getchar.rb +40 -0
- data/lib/themigrator/ui/log_area.rb +173 -0
- data/lib/themigrator/ui/progress_window.rb +52 -0
- data/lib/themigrator/ui/title_bar.rb +31 -0
- data/lib/themigrator/ui.rb +128 -118
- data/lib/themigrator/version.rb +1 -1
- data/lib/themigrator.rb +8 -10
- data/themigrator.gemspec +15 -14
- metadata +23 -2
data/lib/themigrator/script.rb
CHANGED
@@ -5,7 +5,7 @@ module Themigrator
|
|
5
5
|
attr_reader :script, :log_file, :attr
|
6
6
|
|
7
7
|
# new creates a new script but doesn't start until called run
|
8
|
-
# Arguments:
|
8
|
+
# Arguments:
|
9
9
|
# * script : Fullpath of the file to execute
|
10
10
|
# * log_file: Fullpath of the file to save the log
|
11
11
|
# * attr: Extra attributes that could be handy (not used
|
@@ -16,7 +16,7 @@ module Themigrator
|
|
16
16
|
@attr = attr
|
17
17
|
@script = script
|
18
18
|
@log_file = log_file
|
19
|
-
@log_fd = File.open(@log_file,
|
19
|
+
@log_fd = File.open(@log_file, 'w', 0o600)
|
20
20
|
@wd_dir = File.dirname(script)
|
21
21
|
@pid = nil
|
22
22
|
@thread = nil
|
@@ -31,36 +31,36 @@ module Themigrator
|
|
31
31
|
|
32
32
|
def run
|
33
33
|
@thread = Thread.new do
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
34
|
+
@pid = Process.spawn(@script,
|
35
|
+
err: @log_fd,
|
36
|
+
out: @log_fd,
|
37
|
+
chdir: @wd_dir)
|
38
|
+
pid, status = Process.wait2(@pid)
|
39
|
+
@end_time = Time.now
|
40
|
+
close
|
41
|
+
return_code = if status.signaled?
|
42
|
+
signal = Signal.list.find do |_k, v|
|
43
|
+
v == status.termsig
|
44
|
+
end
|
45
|
+
signal.nil? ? :unknown_signal : signal[0].downcase.to_sym
|
46
|
+
else
|
47
|
+
status.exitstatus
|
48
|
+
end
|
49
|
+
|
50
|
+
Thread.new { invoke_cbks } if @finish_cbk
|
51
|
+
|
52
|
+
return_code
|
53
53
|
end
|
54
54
|
sleep 0
|
55
55
|
end
|
56
56
|
|
57
57
|
def invoke_cbks
|
58
|
-
@finish_cbk.call(self)
|
58
|
+
@finish_cbk.call(self)
|
59
59
|
end
|
60
60
|
|
61
61
|
# Waits for the process to be over
|
62
62
|
def wait(seconds = nil)
|
63
|
-
@thread.join(seconds)
|
63
|
+
@thread.join(seconds)
|
64
64
|
end
|
65
65
|
|
66
66
|
def exitcode
|
@@ -69,26 +69,24 @@ module Themigrator
|
|
69
69
|
|
70
70
|
def stop
|
71
71
|
if running?
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
-
wait
|
72
|
+
send_int
|
73
|
+
send_kill if wait(5).nil?
|
74
|
+
wait
|
77
75
|
end
|
78
76
|
end
|
79
77
|
|
80
78
|
def success?
|
81
|
-
exitcode
|
79
|
+
exitcode.zero?
|
82
80
|
end
|
83
81
|
|
84
82
|
private
|
85
83
|
|
86
84
|
def running?
|
87
|
-
@thread
|
85
|
+
@thread && @thread.alive?
|
88
86
|
end
|
89
87
|
|
90
88
|
def send_signal(signal)
|
91
|
-
Process.kill(signal.to_s.upcase
|
89
|
+
Process.kill(signal.to_s.upcase, @pid)
|
92
90
|
end
|
93
91
|
|
94
92
|
def send_int
|
@@ -102,6 +100,5 @@ module Themigrator
|
|
102
100
|
def close
|
103
101
|
@log_fd.close
|
104
102
|
end
|
105
|
-
|
106
103
|
end
|
107
104
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'themigrator/script'
|
2
2
|
|
3
|
-
|
4
3
|
module Themigrator
|
5
4
|
class ScriptPool
|
6
5
|
attr_reader :scripts, :run_time
|
@@ -10,10 +9,10 @@ module Themigrator
|
|
10
9
|
@scripts = []
|
11
10
|
end
|
12
11
|
|
13
|
-
def add_script(script,log_file, attr = {}
|
12
|
+
def add_script(script, log_file, attr = {}, &block)
|
14
13
|
s = Script.new(script, log_file, attr, &block)
|
15
14
|
@lock.synchronize do
|
16
|
-
|
15
|
+
@scripts << s
|
17
16
|
end
|
18
17
|
s
|
19
18
|
end
|
@@ -25,8 +24,8 @@ module Themigrator
|
|
25
24
|
def run_and_wait
|
26
25
|
start = Time.now
|
27
26
|
@lock.synchronize do
|
28
|
-
|
29
|
-
|
27
|
+
start_scripts
|
28
|
+
collect_result
|
30
29
|
end
|
31
30
|
ensure
|
32
31
|
@run_time = Time.now - start
|
@@ -36,33 +35,30 @@ module Themigrator
|
|
36
35
|
|
37
36
|
def start_scripts
|
38
37
|
@scripts.each do |s|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
38
|
+
Thread.new do
|
39
|
+
s.run
|
40
|
+
s.wait
|
41
|
+
@queue << s
|
42
|
+
end
|
44
43
|
end
|
45
44
|
end
|
46
45
|
|
47
|
-
def
|
46
|
+
def collect_result
|
48
47
|
running_scripts = @scripts.size
|
49
48
|
success = true
|
50
|
-
while running_scripts
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
49
|
+
while running_scripts.nonzero?
|
50
|
+
s = @queue.pop
|
51
|
+
unless s.success?
|
52
|
+
stop_and_kill
|
53
|
+
success = false
|
54
|
+
end
|
55
|
+
running_scripts -= 1
|
57
56
|
end
|
58
57
|
success
|
59
58
|
end
|
60
59
|
|
61
60
|
def stop_and_kill
|
62
|
-
@scripts.each
|
63
|
-
s.stop
|
64
|
-
}
|
61
|
+
@scripts.each(&:stop)
|
65
62
|
end
|
66
|
-
|
67
63
|
end
|
68
64
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
# From: http://www.alecjacobson.com/weblog/?p=75
|
3
|
+
module Themigrator
|
4
|
+
class UI
|
5
|
+
module Getchar
|
6
|
+
KEY2SYM = Hash.new { |h, k| h[k] = k }
|
7
|
+
KEY2SYM["\e[D"] = :left
|
8
|
+
KEY2SYM["\e[C"] = :right
|
9
|
+
KEY2SYM["\r"] = :enter
|
10
|
+
KEY2SYM["\e"] = :esc
|
11
|
+
KEY2SYM["\e[6~"] = :npage
|
12
|
+
KEY2SYM["\e[5~"] = :ppage
|
13
|
+
KEY2SYM["\u0003"] = :c_c
|
14
|
+
KEY2SYM["\e[B"] = :scroll_down
|
15
|
+
KEY2SYM["\e[A"] = :scroll_up
|
16
|
+
|
17
|
+
def getchar
|
18
|
+
begin
|
19
|
+
c = STDIN.getc.chr
|
20
|
+
# gather next two characters of special keys
|
21
|
+
if c == "\e"
|
22
|
+
extra_thread = Thread.new do
|
23
|
+
c += STDIN.getc.chr
|
24
|
+
c += STDIN.getc.chr
|
25
|
+
c += STDIN.getc.chr if ["\e[6", "\e[5"].include?(c)
|
26
|
+
end
|
27
|
+
# wait just long enough for special keys to get swallowed
|
28
|
+
extra_thread.join(0.00100)
|
29
|
+
# kill thread so not-so-long special keys don't wait on getc
|
30
|
+
extra_thread.kill
|
31
|
+
end
|
32
|
+
rescue => e
|
33
|
+
log_exception(e)
|
34
|
+
ensure
|
35
|
+
end
|
36
|
+
KEY2SYM[c]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'curses'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Themigrator
|
5
|
+
class UI
|
6
|
+
module LogArea
|
7
|
+
include Curses
|
8
|
+
|
9
|
+
class LogWindow < Window
|
10
|
+
attr_accessor :role, :action
|
11
|
+
def initialize(height, width, top, left)
|
12
|
+
@height = height
|
13
|
+
@width = width
|
14
|
+
@top = top
|
15
|
+
@left = left
|
16
|
+
@inner_win = ::Curses::Window.new(height - 2, width - 2, top + 1, left + 1)
|
17
|
+
@inner_win.scrollok(true)
|
18
|
+
@buffer = ''
|
19
|
+
@scroll = -1
|
20
|
+
super
|
21
|
+
idlok(true)
|
22
|
+
end
|
23
|
+
|
24
|
+
def redraw
|
25
|
+
clear
|
26
|
+
@inner_win.clear
|
27
|
+
setpos(0, 0)
|
28
|
+
box('|', '-')
|
29
|
+
addstr(name)
|
30
|
+
log('redraw')
|
31
|
+
refresh
|
32
|
+
render(true)
|
33
|
+
end
|
34
|
+
|
35
|
+
def nextpage
|
36
|
+
@scroll += 5
|
37
|
+
@scroll = -1 if @scroll > -1
|
38
|
+
log("@scroll = #{@scroll}")
|
39
|
+
end
|
40
|
+
|
41
|
+
def prevpage
|
42
|
+
@scroll -= 5
|
43
|
+
buffer_lines = @buffer.split("\n").size
|
44
|
+
offset_lines = buffer_lines - @height + (2 + 5)
|
45
|
+
|
46
|
+
log("@scroll = #{@scroll} and offset_lines - #{offset_lines} | buffer_lines #{buffer_lines} | @height = #{@height}")
|
47
|
+
@height
|
48
|
+
@scroll = -offset_lines if @scroll < -offset_lines
|
49
|
+
end
|
50
|
+
|
51
|
+
def name
|
52
|
+
'%-13s/%-10s' % [action, role]
|
53
|
+
end
|
54
|
+
|
55
|
+
def append(msg)
|
56
|
+
@buffer << msg
|
57
|
+
end
|
58
|
+
|
59
|
+
def render(force = false)
|
60
|
+
# refresh
|
61
|
+
render_inner_win(force)
|
62
|
+
end
|
63
|
+
|
64
|
+
def log(msg)
|
65
|
+
ui_log("#{name}##{msg}")
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def <<(msg)
|
71
|
+
@inner_win << msg
|
72
|
+
end
|
73
|
+
|
74
|
+
def render_inner_win(force = false)
|
75
|
+
new_content = content_changed?
|
76
|
+
return unless new_content || force
|
77
|
+
log('render')
|
78
|
+
@inner_win.clear
|
79
|
+
@inner_win << @buffer.split("\n")[0..@scroll].join("\n")
|
80
|
+
@inner_win.refresh
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_content_for_win
|
84
|
+
@buffer.split("\n")[0..@scroll].join("\n")
|
85
|
+
end
|
86
|
+
|
87
|
+
def content_changed?
|
88
|
+
new_content = get_content_for_win
|
89
|
+
if @previous == new_content
|
90
|
+
nil
|
91
|
+
else
|
92
|
+
@previous = get_content_for_win
|
93
|
+
new_content
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def initialize_log_area(roles)
|
99
|
+
@la_roles = roles
|
100
|
+
@la_scripts = Themigrator::Migration::SCRIPTS
|
101
|
+
@la_actions = Themigrator::Migration::ACTIONS
|
102
|
+
@la_current_action = nil
|
103
|
+
@la_lines = lines
|
104
|
+
@la_cols = cols
|
105
|
+
@la_mutex = Mutex.new
|
106
|
+
# @windows = Hash.new {|h,a| h[a] = {} }
|
107
|
+
# @windows[action][role]
|
108
|
+
@la_windows
|
109
|
+
@la_windows = Hash.new do |h, action|
|
110
|
+
role_windows = {}
|
111
|
+
@la_roles.each_with_index do |role, n|
|
112
|
+
column_height = @la_lines - 4
|
113
|
+
column_width = @la_cols / @la_roles.size
|
114
|
+
top = 2
|
115
|
+
left = column_width * n
|
116
|
+
w = LogWindow.new(column_height, column_width - 1, top, left)
|
117
|
+
w.role = role
|
118
|
+
w.action = action
|
119
|
+
role_windows[role] = w
|
120
|
+
end
|
121
|
+
h[action] = role_windows
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def la_initialize?
|
126
|
+
defined?(@la_roles)
|
127
|
+
end
|
128
|
+
|
129
|
+
def la_npage
|
130
|
+
la_each_window(&:nextpage)
|
131
|
+
end
|
132
|
+
|
133
|
+
def la_ppage
|
134
|
+
la_each_window(&:prevpage)
|
135
|
+
end
|
136
|
+
|
137
|
+
def la_switch_action(action)
|
138
|
+
return if @la_current_action == action || @la_roles.empty?
|
139
|
+
@la_current_action = action.to_s
|
140
|
+
@la_roles.each do |role|
|
141
|
+
la_get_window(action, role).redraw
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def la_append(action, role, msg)
|
146
|
+
@la_mutex.synchronize do
|
147
|
+
la_get_window(action, role).append(msg)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def la_render
|
152
|
+
la_each_window do |w, _role|
|
153
|
+
w.render
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def la_each_window
|
160
|
+
@la_roles.each do |role|
|
161
|
+
@la_mutex.synchronize do
|
162
|
+
yield la_get_window(@la_current_action, role), role
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def la_get_window(action, role)
|
168
|
+
fuck @la_windows[action][role]
|
169
|
+
@la_windows[action][role]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
module Themigrator
|
3
|
+
class UI
|
4
|
+
module ProgressWindow
|
5
|
+
include Curses
|
6
|
+
def render_progress
|
7
|
+
progress_window.clear
|
8
|
+
progress_window.setpos(0, 0)
|
9
|
+
progress_window.addstr(build_status_line)
|
10
|
+
progress_window.setpos(1, 0)
|
11
|
+
progress_window.addstr(@pw_status) if defined?(@pw_status)
|
12
|
+
progress_window.refresh
|
13
|
+
end
|
14
|
+
|
15
|
+
def pg_set_action(action)
|
16
|
+
@pg_action = action
|
17
|
+
end
|
18
|
+
|
19
|
+
def progress_window
|
20
|
+
@progress_widnow ||= Window.new(@lines - 2, @cols, @lines - 2, 0).tap do |w|
|
21
|
+
w.attrset(A_DIM)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def say(*msg)
|
26
|
+
@pw_status = msg.map(&:to_s).join(' ')
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_status_line
|
30
|
+
scripts = Themigrator::Migration::SCRIPTS
|
31
|
+
width = scripts.map(&:size).inject(&:+)
|
32
|
+
free_space = @cols - width
|
33
|
+
sep_space = scripts.size - 1
|
34
|
+
inline_space = ((free_space - sep_space) / scripts.size - 1) / 2
|
35
|
+
|
36
|
+
action_msg = []
|
37
|
+
scripts.map do |script|
|
38
|
+
a = ''
|
39
|
+
inline_space.times { a << ' ' }
|
40
|
+
a << if defined?(@pg_action) && @pg_action.to_s == script.to_s
|
41
|
+
script.upcase
|
42
|
+
else
|
43
|
+
script.downcase
|
44
|
+
end
|
45
|
+
inline_space.times { a << ' ' }
|
46
|
+
action_msg << a
|
47
|
+
end
|
48
|
+
action_msg.join('>')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
module Themigrator
|
3
|
+
class UI
|
4
|
+
module TitleBar
|
5
|
+
include Curses
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
def render_title
|
10
|
+
now_str = Time.now.strftime('%d %b - %H:%M.%S')
|
11
|
+
time_window.setpos(0, 0)
|
12
|
+
time_window.addstr(now_str)
|
13
|
+
time_window.refresh
|
14
|
+
end
|
15
|
+
|
16
|
+
def title_window
|
17
|
+
@title_window ||= Window.new(1, @cols - 18, 0, 0).tap do |w|
|
18
|
+
w.attrset(A_BOLD)
|
19
|
+
w.addstr('The migrator')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def time_window
|
24
|
+
width = 18
|
25
|
+
@time_window ||= Window.new(1, 18, 0, @cols - 18).tap do |w|
|
26
|
+
w.attrset(A_DIM)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|