tapsoob 0.1.10

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.
@@ -0,0 +1,236 @@
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 = STDERR)
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
+ @title_width = 14
28
+ @format = "%-#{@title_width}s %3d%% %s %s"
29
+ @format_arguments = [:title, :percentage, :bar, :stat]
30
+ clear
31
+ show
32
+ end
33
+ attr_reader :title
34
+ attr_reader :current
35
+ attr_reader :total
36
+ attr_accessor :start_time
37
+
38
+ private
39
+ def fmt_bar
40
+ bar_width = do_percentage * @terminal_width / 100
41
+ sprintf("|%s%s|",
42
+ @bar_mark * bar_width,
43
+ " " * (@terminal_width - bar_width))
44
+ end
45
+
46
+ def fmt_percentage
47
+ do_percentage
48
+ end
49
+
50
+ def fmt_stat
51
+ if @finished_p then elapsed else eta end
52
+ end
53
+
54
+ def fmt_stat_for_file_transfer
55
+ if @finished_p then
56
+ sprintf("%s %s %s", bytes, transfer_rate, elapsed)
57
+ else
58
+ sprintf("%s %s %s", bytes, transfer_rate, eta)
59
+ end
60
+ end
61
+
62
+ def fmt_title
63
+ @title[0,(@title_width - 1)] + ":"
64
+ end
65
+
66
+ def convert_bytes (bytes)
67
+ if bytes < 1024
68
+ sprintf("%6dB", bytes)
69
+ elsif bytes < 1024 * 1000 # 1000kb
70
+ sprintf("%5.1fKB", bytes.to_f / 1024)
71
+ elsif bytes < 1024 * 1024 * 1000 # 1000mb
72
+ sprintf("%5.1fMB", bytes.to_f / 1024 / 1024)
73
+ else
74
+ sprintf("%5.1fGB", bytes.to_f / 1024 / 1024 / 1024)
75
+ end
76
+ end
77
+
78
+ def transfer_rate
79
+ bytes_per_second = @current.to_f / (Time.now - @start_time)
80
+ sprintf("%s/s", convert_bytes(bytes_per_second))
81
+ end
82
+
83
+ def bytes
84
+ convert_bytes(@current)
85
+ end
86
+
87
+ def format_time (t)
88
+ t = t.to_i
89
+ sec = t % 60
90
+ min = (t / 60) % 60
91
+ hour = t / 3600
92
+ sprintf("%02d:%02d:%02d", hour, min, sec);
93
+ end
94
+
95
+ # ETA stands for Estimated Time of Arrival.
96
+ def eta
97
+ if @current == 0
98
+ "ETA: --:--:--"
99
+ else
100
+ elapsed = Time.now - @start_time
101
+ eta = elapsed * @total / @current - elapsed;
102
+ sprintf("ETA: %s", format_time(eta))
103
+ end
104
+ end
105
+
106
+ def elapsed
107
+ elapsed = Time.now - @start_time
108
+ sprintf("Time: %s", format_time(elapsed))
109
+ end
110
+
111
+ def eol
112
+ if @finished_p then "\n" else "\r" end
113
+ end
114
+
115
+ def do_percentage
116
+ if @total.zero?
117
+ 100
118
+ else
119
+ @current * 100 / @total
120
+ end
121
+ end
122
+
123
+ def get_width
124
+ # FIXME: I don't know how portable it is.
125
+ default_width = 80
126
+ begin
127
+ tiocgwinsz = 0x5413
128
+ data = [0, 0, 0, 0].pack("SSSS")
129
+ if @out.ioctl(tiocgwinsz, data) >= 0 then
130
+ rows, cols, xpixels, ypixels = data.unpack("SSSS")
131
+ if cols > 0 then cols else default_width end
132
+ else
133
+ default_width
134
+ end
135
+ rescue Exception
136
+ default_width
137
+ end
138
+ end
139
+
140
+ def show
141
+ arguments = @format_arguments.map {|method|
142
+ method = sprintf("fmt_%s", method)
143
+ send(method)
144
+ }
145
+ line = sprintf(@format, *arguments)
146
+
147
+ width = get_width
148
+ if line.length == width - 1
149
+ @out.print(line + eol)
150
+ @out.flush
151
+ elsif line.length >= width
152
+ @terminal_width = [@terminal_width - (line.length - width + 1), 0].max
153
+ if @terminal_width == 0 then @out.print(line + eol) else show end
154
+ else # line.length < width - 1
155
+ @terminal_width += width - line.length + 1
156
+ show
157
+ end
158
+ @previous_time = Time.now
159
+ end
160
+
161
+ def show_if_needed
162
+ if @total.zero?
163
+ cur_percentage = 100
164
+ prev_percentage = 0
165
+ else
166
+ cur_percentage = (@current * 100 / @total).to_i
167
+ prev_percentage = (@previous * 100 / @total).to_i
168
+ end
169
+
170
+ # Use "!=" instead of ">" to support negative changes
171
+ if cur_percentage != prev_percentage ||
172
+ Time.now - @previous_time >= 1 || @finished_p
173
+ show
174
+ end
175
+ end
176
+
177
+ public
178
+ def clear
179
+ @out.print "\r"
180
+ @out.print(" " * (get_width - 1))
181
+ @out.print "\r"
182
+ end
183
+
184
+ def finish
185
+ @current = @total
186
+ @finished_p = true
187
+ show
188
+ end
189
+
190
+ def finished?
191
+ @finished_p
192
+ end
193
+
194
+ def file_transfer_mode
195
+ @format_arguments = [:title, :percentage, :bar, :stat_for_file_transfer]
196
+ end
197
+
198
+ def format= (format)
199
+ @format = format
200
+ end
201
+
202
+ def format_arguments= (arguments)
203
+ @format_arguments = arguments
204
+ end
205
+
206
+ def halt
207
+ @finished_p = true
208
+ show
209
+ end
210
+
211
+ def inc (step = 1)
212
+ @current += step
213
+ @current = @total if @current > @total
214
+ show_if_needed
215
+ @previous = @current
216
+ end
217
+
218
+ def set (count)
219
+ if count < 0 || count > @total
220
+ raise "invalid count: #{count} (total: #{@total})"
221
+ end
222
+ @current = count
223
+ show_if_needed
224
+ @previous = @current
225
+ end
226
+
227
+ def inspect
228
+ "#<ProgressBar:#{@current}/#{@total}>"
229
+ end
230
+ end
231
+
232
+ class ReversedProgressBar < ProgressBar
233
+ def do_percentage
234
+ 100 - super
235
+ end
236
+ end
@@ -0,0 +1,11 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'tapsoob'
3
+ require 'rails'
4
+
5
+ module Tapsoob
6
+ class Railtie < Rails::Railtie
7
+ rake_tasks do
8
+ load "tasks/tapsoob.rake"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,83 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'sequel'
3
+ require 'sequel/extensions/schema_dumper'
4
+ require 'sequel/extensions/migration'
5
+ require 'json'
6
+
7
+ module Tapsoob
8
+ module Schema
9
+ extend self
10
+
11
+ def dump(database_url)
12
+ db = Sequel.connect(database_url)
13
+ db.dump_schema_migration(:indexes => false)
14
+ end
15
+
16
+ def dump_table(database_url, table)
17
+ table = table.to_sym
18
+ Sequel.connect(database_url) do |db|
19
+ <<END_MIG
20
+ Class.new(Sequel::Migration) do
21
+ def up
22
+ #{db.dump_table_schema(table.identifier, :indexes => false)}
23
+ end
24
+
25
+ def down
26
+ drop_table("#{table}") if @db.table_exists?("#{table}")
27
+ end
28
+ end
29
+ END_MIG
30
+ end
31
+ end
32
+
33
+ def indexes(database_url)
34
+ db = Sequel.connect(database_url)
35
+ db.dump_indexes_migration
36
+ end
37
+
38
+ def indexes_individual(database_url)
39
+ idxs = {}
40
+ Sequel.connect(database_url) do |db|
41
+ tables = db.tables
42
+ tables.each do |table|
43
+ idxs[table] = db.send(:dump_table_indexes, table, :add_index, {}).split("\n")
44
+ end
45
+ end
46
+
47
+ idxs.each do |table, indexes|
48
+ idxs[table] = indexes.map do |idx|
49
+ <<END_MIG
50
+ Class.new(Sequel::Migration) do
51
+ def up
52
+ #{idx}
53
+ end
54
+ end
55
+ END_MIG
56
+ end
57
+ end
58
+ JSON.generate(idxs)
59
+ end
60
+
61
+ def load(database_url, schema)
62
+ Sequel.connect(database_url) do |db|
63
+ klass = eval(schema)
64
+ klass.apply(db, :down)
65
+ klass.apply(db, :up)
66
+ end
67
+ end
68
+
69
+ def load_indexes(database_url, indexes)
70
+ Sequel.connect(database_url) do |db|
71
+ eval(indexes).apply(db, :up)
72
+ end
73
+ end
74
+
75
+ def reset_db_sequences(database_url)
76
+ db = Sequel.connect(database_url)
77
+ return unless db.respond_to?(:reset_primary_key_sequence)
78
+ db.tables.each do |table|
79
+ db.reset_primary_key_sequence(table)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,179 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'zlib'
3
+
4
+ require 'tapsoob/errors'
5
+ require 'tapsoob/chunksize'
6
+ require 'tapsoob/schema'
7
+
8
+ module Tapsoob
9
+ module Utils
10
+ extend self
11
+
12
+ def windows?
13
+ return @windows if defined?(@windows)
14
+ require 'rbconfig'
15
+ @windows = !!(::RbConfig::CONFIG['host_os'] =~ /mswin|mingw/)
16
+ end
17
+
18
+ def bin(cmd)
19
+ cmd = "#{cmd}.cmd" if windows?
20
+ cmd
21
+ end
22
+
23
+ def checksum(data)
24
+ Zlib.crc32(data)
25
+ end
26
+
27
+ def valid_data?(data, crc32)
28
+ Zlib.crc32(data) == crc32.to_i
29
+ end
30
+
31
+ def base64encode(data)
32
+ [data].pack("m")
33
+ end
34
+
35
+ def base64decode(data)
36
+ data.unpack("m").first
37
+ end
38
+
39
+ def format_data(data, opts = {})
40
+ return {} if data.size == 0
41
+ string_columns = opts[:string_columns] || []
42
+ schema = opts[:schema] || []
43
+ table = opts[:table]
44
+
45
+ max_lengths = schema.inject({}) do |hash, (column, meta)|
46
+ if meta[:db_type] =~ /^varchar\((\d+)\)/
47
+ hash.update(column => $1.to_i)
48
+ end
49
+ hash
50
+ end
51
+
52
+ header = data[0].keys
53
+ only_data = data.collect do |row|
54
+ row = blobs_to_string(row, string_columns)
55
+ row.each do |column, data|
56
+ if data.to_s.length > (max_lengths[column] || data.to_s.length)
57
+ raise Tapsoob::InvalidData.new(<<-ERROR)
58
+ Detected data that exceeds the length limitation of its column. This is
59
+ generally due to the fact that SQLite does not enforce length restrictions.
60
+
61
+ Table : #{table}
62
+ Column : #{column}
63
+ Type : #{schema.detect{|s| s.first == column}.last[:db_type]}
64
+ Data : #{data}
65
+ ERROR
66
+ end
67
+ end
68
+ header.collect { |h| row[h] }
69
+ end
70
+ { :header => header, :data => only_data }
71
+ end
72
+
73
+ # mysql text and blobs fields are handled the same way internally
74
+ # this is not true for other databases so we must check if the field is
75
+ # actually text and manually convert it back to a string
76
+ def incorrect_blobs(db, table)
77
+ return [] if (db.url =~ /mysql:\/\//).nil?
78
+
79
+ columns = []
80
+ db.schema(table).each do |data|
81
+ column, cdata = data
82
+ columns << column if cdata[:db_type] =~ /text/
83
+ end
84
+ columns
85
+ end
86
+
87
+ def blobs_to_string(row, columns)
88
+ return row if columns.size == 0
89
+ columns.each do |c|
90
+ row[c] = row[c].to_s if row[c].kind_of?(Sequel::SQL::Blob)
91
+ end
92
+ row
93
+ end
94
+
95
+ def calculate_chunksize(old_chunksize)
96
+ c = Tapsoob::Chunksize.new(old_chunksize)
97
+
98
+ begin
99
+ c.start_time = Time.now
100
+ c.time_in_db = yield c
101
+ rescue Errno::EPIPE
102
+ c.retries += 1
103
+ raise if c.retries > 2
104
+
105
+ # we got disconnected, the chunksize could be too large
106
+ # reset the chunksize based on the number of retries
107
+ c.reset_chunksize
108
+ retry
109
+ end
110
+
111
+ c.end_time = Time.now
112
+ c.calc_new_chunksize
113
+ end
114
+
115
+ def export_schema(dump_path, table, schema_data)
116
+ File.open(File.join(dump_path, "schemas", "#{table}.rb"), 'w') do |file|
117
+ file.write(schema_data)
118
+ end
119
+ end
120
+
121
+ def export_indexes(dump_path, table, index_data)
122
+ data = [index_data]
123
+ if File.exists?(File.join(dump_path, "indexes", "#{table}.json"))
124
+ previous_data = JSON.parse(File.read(File.join(dump_path, "indexes", "#{table}.json")))
125
+ data = data + previous_data
126
+ end
127
+
128
+ File.open(File.join(dump_path, "indexes", "#{table}.json"), 'w') do |file|
129
+ file.write(JSON.generate(data))
130
+ end
131
+ end
132
+
133
+ def export_rows(dump_path, table, row_data)
134
+ data = row_data
135
+ if File.exists?(File.join(dump_path, "data", "#{table}.json"))
136
+ previous_data = JSON.parse(File.read(File.join(dump_path, "data", "#{table}.json")))
137
+ data[:data] = previous_data["data"] + row_data[:data]
138
+ end
139
+
140
+ File.open(File.join(dump_path, "data", "#{table}.json"), 'w') do |file|
141
+ file.write(JSON.generate(data))
142
+ end
143
+ end
144
+
145
+ def load_schema(dump_path, database_url, table)
146
+ schema = File.join(dump_path, "schemas", "#{table}.rb")
147
+ schema_bin(:load, database_url, schema.to_s)
148
+ end
149
+
150
+ def load_indexes(database_url, index)
151
+ Tapsoob::Schema.load_indexes(database_url, index)
152
+ end
153
+
154
+ def schema_bin(*args)
155
+ bin_path = File.expand_path("#{File.dirname(__FILE__)}/../../bin/#{bin('schema')}")
156
+ `"#{bin_path}" #{args.map { |a| "'#{a}'" }.join(' ')}`
157
+ end
158
+
159
+ def primary_key(db, table)
160
+ db.schema(table).select { |c| c[1][:primary_key] }.map { |c| c[0] }
161
+ end
162
+
163
+ def single_integer_primary_key(db, table)
164
+ table = table.to_sym.identifier unless table.kind_of?(Sequel::SQL::Identifier)
165
+ keys = db.schema(table).select { |c| c[1][:primary_key] and c[1][:type] == :integer }
166
+ not keys.nil? and keys.size == 1
167
+ end
168
+
169
+ def order_by(db, table)
170
+ pkey = primary_key(db, table)
171
+ if pkey
172
+ pkey.kind_of?(Array) ? pkey : [pkey.to_sym]
173
+ else
174
+ table = table.to_sym.identifier unless table.kind_of?(Sequel::SQL::Identifier)
175
+ db[table].columns
176
+ end
177
+ end
178
+ end
179
+ end