matthewtodd-taps 0.2.19

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,33 @@
1
+ require 'sequel'
2
+ require 'sqlite3'
3
+
4
+ module Taps
5
+ def self.version_yml
6
+ @@version_yml ||= YAML.load(File.read(File.dirname(__FILE__) + '/../../VERSION.yml'))
7
+ end
8
+
9
+ def self.version
10
+ "#{version_yml[:major]}.#{version_yml[:minor]}.#{version_yml[:patch]}"
11
+ end
12
+
13
+ def self.compatible_version
14
+ "#{version_yml[:major]}.#{version_yml[:minor]}"
15
+ end
16
+
17
+ class Config
18
+ class << self
19
+ attr_accessor :taps_database_url
20
+ attr_accessor :login, :password, :database_url, :remote_url
21
+ attr_accessor :chunksize
22
+
23
+ def verify_database_url
24
+ db = Sequel.connect(self.database_url)
25
+ db.tables
26
+ db.disconnect
27
+ rescue Object => e
28
+ puts "Failed to connect to database:\n #{e.class} -> #{e}"
29
+ exit 1
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,62 @@
1
+ require 'thread'
2
+
3
+ Sequel::Model.db = Sequel.connect(Taps::Config.taps_database_url)
4
+
5
+ class DbSession < Sequel::Model
6
+ plugin :schema
7
+ set_schema do
8
+ primary_key :id
9
+ text :key
10
+ text :database_url
11
+ timestamp :started_at
12
+ timestamp :last_access
13
+ end
14
+
15
+ @@connections = {}
16
+ @@mutex = Mutex.new
17
+
18
+ def connection
19
+ @@mutex.synchronize {
20
+ conn =
21
+ if @@connections.key?(key)
22
+ @@connections[key].first
23
+ else
24
+ Sequel.connect(database_url)
25
+ end
26
+ @@connections[key] = [conn, Time.now]
27
+ return conn
28
+ }
29
+ end
30
+
31
+ def disconnect
32
+ @@mutex.synchronize {
33
+ if @@connections.key?(key)
34
+ conn, time = @@connections.delete(key)
35
+ conn.disconnect
36
+ end
37
+ }
38
+ end
39
+
40
+ # Removes connections that have not been accessed within the
41
+ # past thirty seconds.
42
+ def self.cleanup
43
+ @@mutex.synchronize {
44
+ now = Time.now
45
+ @@connections.each do |key, (conn, time)|
46
+ if now - time > 30
47
+ @@connections.delete(key)
48
+ conn.disconnect
49
+ end
50
+ end
51
+ }
52
+ end
53
+
54
+ Thread.new {
55
+ while true
56
+ sleep 30
57
+ cleanup
58
+ end
59
+ }.run
60
+ end
61
+
62
+ DbSession.create_table! unless DbSession.table_exists?
@@ -0,0 +1,236 @@
1
+ #
2
+ # Ruby/ProgressBar - a text progress bar library
3
+ #
4
+ # Copyright (C) 2001-2005 Satoru Takabayashi <satoru@namazu.org>
5
+ # All rights reserved.
6
+ # This is free software with ABSOLUTELY NO WARRANTY.
7
+ #
8
+ # You can redistribute it and/or modify it under the terms
9
+ # of Ruby's license.
10
+ #
11
+
12
+ class ProgressBar
13
+ VERSION = "0.9"
14
+
15
+ def initialize (title, total, out = STDERR)
16
+ @title = title
17
+ @total = total
18
+ @out = out
19
+ @terminal_width = 80
20
+ @bar_mark = "="
21
+ @current = 0
22
+ @previous = 0
23
+ @finished_p = false
24
+ @start_time = Time.now
25
+ @previous_time = @start_time
26
+ @title_width = 14
27
+ @format = "%-#{@title_width}s %3d%% %s %s"
28
+ @format_arguments = [:title, :percentage, :bar, :stat]
29
+ clear
30
+ show
31
+ end
32
+ attr_reader :title
33
+ attr_reader :current
34
+ attr_reader :total
35
+ attr_accessor :start_time
36
+
37
+ private
38
+ def fmt_bar
39
+ bar_width = do_percentage * @terminal_width / 100
40
+ sprintf("|%s%s|",
41
+ @bar_mark * bar_width,
42
+ " " * (@terminal_width - bar_width))
43
+ end
44
+
45
+ def fmt_percentage
46
+ do_percentage
47
+ end
48
+
49
+ def fmt_stat
50
+ if @finished_p then elapsed else eta end
51
+ end
52
+
53
+ def fmt_stat_for_file_transfer
54
+ if @finished_p then
55
+ sprintf("%s %s %s", bytes, transfer_rate, elapsed)
56
+ else
57
+ sprintf("%s %s %s", bytes, transfer_rate, eta)
58
+ end
59
+ end
60
+
61
+ def fmt_title
62
+ @title[0,(@title_width - 1)] + ":"
63
+ end
64
+
65
+ def convert_bytes (bytes)
66
+ if bytes < 1024
67
+ sprintf("%6dB", bytes)
68
+ elsif bytes < 1024 * 1000 # 1000kb
69
+ sprintf("%5.1fKB", bytes.to_f / 1024)
70
+ elsif bytes < 1024 * 1024 * 1000 # 1000mb
71
+ sprintf("%5.1fMB", bytes.to_f / 1024 / 1024)
72
+ else
73
+ sprintf("%5.1fGB", bytes.to_f / 1024 / 1024 / 1024)
74
+ end
75
+ end
76
+
77
+ def transfer_rate
78
+ bytes_per_second = @current.to_f / (Time.now - @start_time)
79
+ sprintf("%s/s", convert_bytes(bytes_per_second))
80
+ end
81
+
82
+ def bytes
83
+ convert_bytes(@current)
84
+ end
85
+
86
+ def format_time (t)
87
+ t = t.to_i
88
+ sec = t % 60
89
+ min = (t / 60) % 60
90
+ hour = t / 3600
91
+ sprintf("%02d:%02d:%02d", hour, min, sec);
92
+ end
93
+
94
+ # ETA stands for Estimated Time of Arrival.
95
+ def eta
96
+ if @current == 0
97
+ "ETA: --:--:--"
98
+ else
99
+ elapsed = Time.now - @start_time
100
+ eta = elapsed * @total / @current - elapsed;
101
+ sprintf("ETA: %s", format_time(eta))
102
+ end
103
+ end
104
+
105
+ def elapsed
106
+ elapsed = Time.now - @start_time
107
+ sprintf("Time: %s", format_time(elapsed))
108
+ end
109
+
110
+ def eol
111
+ if @finished_p then "\n" else "\r" end
112
+ end
113
+
114
+ def do_percentage
115
+ if @total.zero?
116
+ 100
117
+ else
118
+ @current * 100 / @total
119
+ end
120
+ end
121
+
122
+ def get_width
123
+ # FIXME: I don't know how portable it is.
124
+ default_width = 80
125
+ begin
126
+ tiocgwinsz = 0x5413
127
+ data = [0, 0, 0, 0].pack("SSSS")
128
+ if @out.ioctl(tiocgwinsz, data) >= 0 then
129
+ rows, cols, xpixels, ypixels = data.unpack("SSSS")
130
+ if cols > 0 then cols else default_width end
131
+ else
132
+ default_width
133
+ end
134
+ rescue Exception
135
+ default_width
136
+ end
137
+ end
138
+
139
+ def show
140
+ arguments = @format_arguments.map {|method|
141
+ method = sprintf("fmt_%s", method)
142
+ send(method)
143
+ }
144
+ line = sprintf(@format, *arguments)
145
+
146
+ width = get_width
147
+ if line.length == width - 1
148
+ @out.print(line + eol)
149
+ @out.flush
150
+ elsif line.length >= width
151
+ @terminal_width = [@terminal_width - (line.length - width + 1), 0].max
152
+ if @terminal_width == 0 then @out.print(line + eol) else show end
153
+ else # line.length < width - 1
154
+ @terminal_width += width - line.length + 1
155
+ show
156
+ end
157
+ @previous_time = Time.now
158
+ end
159
+
160
+ def show_if_needed
161
+ if @total.zero?
162
+ cur_percentage = 100
163
+ prev_percentage = 0
164
+ else
165
+ cur_percentage = (@current * 100 / @total).to_i
166
+ prev_percentage = (@previous * 100 / @total).to_i
167
+ end
168
+
169
+ # Use "!=" instead of ">" to support negative changes
170
+ if cur_percentage != prev_percentage ||
171
+ Time.now - @previous_time >= 1 || @finished_p
172
+ show
173
+ end
174
+ end
175
+
176
+ public
177
+ def clear
178
+ @out.print "\r"
179
+ @out.print(" " * (get_width - 1))
180
+ @out.print "\r"
181
+ end
182
+
183
+ def finish
184
+ @current = @total
185
+ @finished_p = true
186
+ show
187
+ end
188
+
189
+ def finished?
190
+ @finished_p
191
+ end
192
+
193
+ def file_transfer_mode
194
+ @format_arguments = [:title, :percentage, :bar, :stat_for_file_transfer]
195
+ end
196
+
197
+ def format= (format)
198
+ @format = format
199
+ end
200
+
201
+ def format_arguments= (arguments)
202
+ @format_arguments = arguments
203
+ end
204
+
205
+ def halt
206
+ @finished_p = true
207
+ show
208
+ end
209
+
210
+ def inc (step = 1)
211
+ @current += step
212
+ @current = @total if @current > @total
213
+ show_if_needed
214
+ @previous = @current
215
+ end
216
+
217
+ def set (count)
218
+ if count < 0 || count > @total
219
+ raise "invalid count: #{count} (total: #{@total})"
220
+ end
221
+ @current = count
222
+ show_if_needed
223
+ @previous = @current
224
+ end
225
+
226
+ def inspect
227
+ "#<ProgressBar:#{@current}/#{@total}>"
228
+ end
229
+ end
230
+
231
+ class ReversedProgressBar < ProgressBar
232
+ def do_percentage
233
+ 100 - super
234
+ end
235
+ end
236
+
@@ -0,0 +1,94 @@
1
+ require 'active_record'
2
+ require 'active_support'
3
+ require 'stringio'
4
+ require 'uri'
5
+
6
+ require 'taps/adapter_hacks'
7
+
8
+ module Taps
9
+ module Schema
10
+ extend self
11
+
12
+ def create_config(url)
13
+ uri = URI.parse(url)
14
+ adapter = uri.scheme
15
+ adapter = 'postgresql' if adapter == 'postgres'
16
+ adapter = 'sqlite3' if adapter == 'sqlite'
17
+ config = {
18
+ 'adapter' => adapter,
19
+ 'database' => uri.path.blank? ? uri.host : uri.path.split('/')[1],
20
+ 'username' => uri.user,
21
+ 'password' => uri.password,
22
+ 'host' => uri.host,
23
+ }
24
+ config = sqlite_config(url) if config['adapter'] == 'sqlite3'
25
+ config
26
+ end
27
+
28
+ def sqlite_config(url)
29
+ m = %r{(sqlite3?)://(.+)}.match(url)
30
+ database = m[2]
31
+ database, q = database.split('?')
32
+ { 'adapter' => 'sqlite3', 'database' => database }
33
+ end
34
+
35
+ def connection(database_url)
36
+ config = create_config(database_url)
37
+ c = ActiveRecord::Base.establish_connection(config)
38
+ Taps::AdapterHacks.load(config['adapter'])
39
+ c
40
+ end
41
+
42
+ def dump(database_url)
43
+ connection(database_url)
44
+
45
+ stream = StringIO.new
46
+ ActiveRecord::SchemaDumper.ignore_tables = []
47
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
48
+ stream.string
49
+ end
50
+
51
+ def dump_without_indexes(database_url)
52
+ schema = dump(database_url)
53
+ schema.split("\n").collect do |line|
54
+ if line =~ /^\s+add_index/
55
+ line = "##{line}"
56
+ end
57
+ line
58
+ end.join("\n")
59
+ end
60
+
61
+ def indexes(database_url)
62
+ schema = dump(database_url)
63
+ schema.split("\n").collect do |line|
64
+ line if line =~ /^\s+add_index/
65
+ end.uniq.join("\n")
66
+ end
67
+
68
+ def load(database_url, schema)
69
+ connection(database_url)
70
+ eval(schema)
71
+ end
72
+
73
+ def load_indexes(database_url, indexes)
74
+ connection(database_url)
75
+
76
+ schema =<<EORUBY
77
+ ActiveRecord::Schema.define do
78
+ #{indexes}
79
+ end
80
+ EORUBY
81
+ eval(schema)
82
+ end
83
+
84
+ def reset_db_sequences(database_url)
85
+ connection(database_url)
86
+
87
+ if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
88
+ ActiveRecord::Base.connection.tables.each do |table|
89
+ ActiveRecord::Base.connection.reset_pk_sequence!(table)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,138 @@
1
+ require 'sinatra/base'
2
+ require 'taps/config'
3
+ require 'taps/utils'
4
+ require 'taps/db_session'
5
+ require 'taps/schema'
6
+
7
+ module Taps
8
+ class Server < Sinatra::Default
9
+ use Rack::Auth::Basic do |login, password|
10
+ login == Taps::Config.login && password == Taps::Config.password
11
+ end
12
+
13
+ error do
14
+ e = request.env['sinatra.error']
15
+ "Taps Server Error: #{e}"
16
+ end
17
+
18
+ before do
19
+ major, minor, patch = request.env['HTTP_TAPS_VERSION'].split('.') rescue []
20
+ unless "#{major}.#{minor}" == Taps.compatible_version
21
+ halt 417, "Taps v#{Taps.compatible_version}.x is required for this server"
22
+ end
23
+ end
24
+
25
+ get '/' do
26
+ "hello"
27
+ end
28
+
29
+ post '/sessions' do
30
+ key = rand(9999999999).to_s
31
+ database_url = Taps::Config.database_url || request.body.string
32
+
33
+ DbSession.create(:key => key, :database_url => database_url, :started_at => Time.now, :last_access => Time.now)
34
+
35
+ "/sessions/#{key}"
36
+ end
37
+
38
+ post '/sessions/:key/tables/:table' do
39
+ session = DbSession.filter(:key => params[:key]).first
40
+ halt 404 unless session
41
+
42
+ gzip_data = request.body.read
43
+ halt 412 unless Taps::Utils.valid_data?(gzip_data, request.env['HTTP_TAPS_CHECKSUM'])
44
+
45
+ rows = Marshal.load(Taps::Utils.gunzip(gzip_data))
46
+
47
+ db = session.connection
48
+ table = db[params[:table].to_sym]
49
+ table.import(rows[:header], rows[:data])
50
+
51
+ "#{rows[:data].size}"
52
+ end
53
+
54
+ post '/sessions/:key/reset_sequences' do
55
+ session = DbSession.filter(:key => params[:key]).first
56
+ halt 404 unless session
57
+
58
+ Taps::Schema.reset_db_sequences(session.database_url)
59
+ end
60
+
61
+ post '/sessions/:key/schema' do
62
+ session = DbSession.filter(:key => params[:key]).first
63
+ halt 404 unless session
64
+
65
+ schema_data = request.body.read
66
+ Taps::Schema.load(session.database_url, schema_data)
67
+ end
68
+
69
+ post '/sessions/:key/indexes' do
70
+ session = DbSession.filter(:key => params[:key]).first
71
+ halt 404 unless session
72
+
73
+ index_data = request.body.read
74
+ Taps::Schema.load_indexes(session.database_url, index_data)
75
+ end
76
+
77
+ get '/sessions/:key/schema' do
78
+ session = DbSession.filter(:key => params[:key]).first
79
+ halt 404 unless session
80
+
81
+ Taps::Schema.dump_without_indexes(session.database_url)
82
+ end
83
+
84
+ get '/sessions/:key/indexes' do
85
+ session = DbSession.filter(:key => params[:key]).first
86
+ halt 404 unless session
87
+
88
+ Taps::Schema.indexes(session.database_url)
89
+ end
90
+
91
+ get '/sessions/:key/tables' do
92
+ session = DbSession.filter(:key => params[:key]).first
93
+ halt 404 unless session
94
+
95
+ db = session.connection
96
+ tables = db.tables
97
+
98
+ tables_with_counts = tables.inject({}) do |accum, table|
99
+ accum[table] = db[table].count
100
+ accum
101
+ end
102
+
103
+ Marshal.dump(tables_with_counts)
104
+ end
105
+
106
+ get '/sessions/:key/tables/:table/:chunk' do
107
+ session = DbSession.filter(:key => params[:key]).first
108
+ halt 404 unless session
109
+
110
+ chunk = params[:chunk].to_i
111
+ chunk = 500 if chunk < 1
112
+
113
+ offset = params[:offset].to_i
114
+ offset = 0 if offset < 0
115
+
116
+ db = session.connection
117
+ table = db[params[:table].to_sym]
118
+ order = Taps::Utils.order_by(db, params[:table].to_sym)
119
+ string_columns = Taps::Utils.incorrect_blobs(db, params[:table].to_sym)
120
+ raw_data = Marshal.dump(Taps::Utils.format_data(table.order(*order).limit(chunk, offset).all, string_columns))
121
+ gzip_data = Taps::Utils.gzip(raw_data)
122
+ response['Taps-Checksum'] = Taps::Utils.checksum(gzip_data).to_s
123
+ response['Content-Type'] = "application/octet-stream"
124
+ gzip_data
125
+ end
126
+
127
+ delete '/sessions/:key' do
128
+ session = DbSession.filter(:key => params[:key]).first
129
+ halt 404 unless session
130
+
131
+ session.disconnect
132
+ session.destroy
133
+
134
+ "ok"
135
+ end
136
+
137
+ end
138
+ end