bricolage 5.8.7
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 +7 -0
- data/README.md +4 -0
- data/bin/bricolage +6 -0
- data/bin/bricolage-jobnet +6 -0
- data/jobclass/create.rb +21 -0
- data/jobclass/exec.rb +17 -0
- data/jobclass/insert-delta.rb +31 -0
- data/jobclass/insert.rb +33 -0
- data/jobclass/load.rb +39 -0
- data/jobclass/my-export.rb +40 -0
- data/jobclass/my-migrate.rb +103 -0
- data/jobclass/noop.rb +13 -0
- data/jobclass/rebuild-drop.rb +37 -0
- data/jobclass/rebuild-rename.rb +49 -0
- data/jobclass/s3-put.rb +19 -0
- data/jobclass/sql.rb +29 -0
- data/jobclass/td-delete.rb +20 -0
- data/jobclass/td-export.rb +30 -0
- data/jobclass/unload.rb +30 -0
- data/jobclass/wait-file.rb +48 -0
- data/lib/bricolage/application.rb +260 -0
- data/lib/bricolage/commandutils.rb +52 -0
- data/lib/bricolage/configloader.rb +126 -0
- data/lib/bricolage/context.rb +108 -0
- data/lib/bricolage/datasource.rb +144 -0
- data/lib/bricolage/eventhandlers.rb +47 -0
- data/lib/bricolage/exception.rb +47 -0
- data/lib/bricolage/filedatasource.rb +42 -0
- data/lib/bricolage/filesystem.rb +165 -0
- data/lib/bricolage/genericdatasource.rb +37 -0
- data/lib/bricolage/job.rb +212 -0
- data/lib/bricolage/jobclass.rb +98 -0
- data/lib/bricolage/jobfile.rb +100 -0
- data/lib/bricolage/jobflow.rb +389 -0
- data/lib/bricolage/jobnetrunner.rb +264 -0
- data/lib/bricolage/jobresult.rb +74 -0
- data/lib/bricolage/logger.rb +52 -0
- data/lib/bricolage/mysqldatasource.rb +223 -0
- data/lib/bricolage/parameters.rb +653 -0
- data/lib/bricolage/postgresconnection.rb +78 -0
- data/lib/bricolage/psqldatasource.rb +449 -0
- data/lib/bricolage/resource.rb +68 -0
- data/lib/bricolage/rubyjobclass.rb +42 -0
- data/lib/bricolage/s3datasource.rb +144 -0
- data/lib/bricolage/script.rb +120 -0
- data/lib/bricolage/sqlstatement.rb +351 -0
- data/lib/bricolage/taskqueue.rb +156 -0
- data/lib/bricolage/tddatasource.rb +116 -0
- data/lib/bricolage/variables.rb +208 -0
- data/lib/bricolage/version.rb +4 -0
- data/lib/bricolage.rb +8 -0
- data/libexec/sqldump +9 -0
- data/libexec/sqldump.Darwin +0 -0
- data/libexec/sqldump.Linux +0 -0
- data/test/all.rb +3 -0
- data/test/home/config/development/database.yml +57 -0
- data/test/home/config/development/password.yml +2 -0
- data/test/home/subsys/separated.job +1 -0
- data/test/home/subsys/separated.sql +1 -0
- data/test/home/subsys/unified.jobnet +1 -0
- data/test/home/subsys/unified.sql.job +5 -0
- data/test/test_filesystem.rb +19 -0
- data/test/test_parameters.rb +401 -0
- data/test/test_variables.rb +114 -0
- metadata +192 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
require 'bricolage/resource'
|
|
2
|
+
require 'bricolage/exception'
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
|
|
6
|
+
module Bricolage
|
|
7
|
+
|
|
8
|
+
class Script
|
|
9
|
+
def initialize
|
|
10
|
+
@tasks = []
|
|
11
|
+
@in_task_block = false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def task(ds)
|
|
15
|
+
raise FatalError, "nested task is not supported" if @in_task_block
|
|
16
|
+
raise ParameterError, "no data source" unless ds
|
|
17
|
+
@in_task_block = true
|
|
18
|
+
begin
|
|
19
|
+
task = ds.new_task
|
|
20
|
+
yield task
|
|
21
|
+
@tasks.push task
|
|
22
|
+
ensure
|
|
23
|
+
@in_task_block = false
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def bind(ctx, vars)
|
|
28
|
+
raise "[BUG] unresolved variables given" unless vars.resolved?
|
|
29
|
+
@tasks.each do |task|
|
|
30
|
+
task.bind(ctx, vars)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def source
|
|
35
|
+
buf = StringIO.new
|
|
36
|
+
first = true
|
|
37
|
+
@tasks.each do |task|
|
|
38
|
+
buf.puts unless first; first = false
|
|
39
|
+
buf.puts task.source
|
|
40
|
+
end
|
|
41
|
+
buf.string
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def run
|
|
45
|
+
result = nil
|
|
46
|
+
@tasks.each do |task|
|
|
47
|
+
result = task.run
|
|
48
|
+
end
|
|
49
|
+
result || JobResult.success
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def run_explain
|
|
53
|
+
@tasks.each do |task|
|
|
54
|
+
if task.respond_to?(:run_explain)
|
|
55
|
+
task.run_explain
|
|
56
|
+
else
|
|
57
|
+
puts "-- task #{task.class} does not support explain; show source"
|
|
58
|
+
puts task.source
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class DataSourceTask
|
|
65
|
+
def initialize(ds)
|
|
66
|
+
@ds = ds
|
|
67
|
+
@actions = []
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
attr_reader :ds
|
|
71
|
+
|
|
72
|
+
def bind(*args)
|
|
73
|
+
@actions.each do |action|
|
|
74
|
+
action.bind(*args)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def run
|
|
79
|
+
result = nil
|
|
80
|
+
@ds.open_for_batch {
|
|
81
|
+
@actions.each do |action|
|
|
82
|
+
result = action.run
|
|
83
|
+
end
|
|
84
|
+
}
|
|
85
|
+
result
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def source
|
|
89
|
+
buf = StringIO.new
|
|
90
|
+
@actions.each do |action|
|
|
91
|
+
buf.puts action.source
|
|
92
|
+
end
|
|
93
|
+
buf.string
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def add(action)
|
|
99
|
+
action.ds = @ds
|
|
100
|
+
@actions.push action
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def each_action(&block)
|
|
104
|
+
@actions.each(&block)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class Action
|
|
108
|
+
extend Forwardable
|
|
109
|
+
|
|
110
|
+
attr_accessor :ds
|
|
111
|
+
|
|
112
|
+
def bind(context, variables)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# abstract def run
|
|
116
|
+
# abstract def source
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
end
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
require 'bricolage/filesystem'
|
|
2
|
+
require 'bricolage/exception'
|
|
3
|
+
|
|
4
|
+
module Bricolage
|
|
5
|
+
|
|
6
|
+
class SQLStatement
|
|
7
|
+
def SQLStatement.delete_where(cond, location = nil)
|
|
8
|
+
for_string("delete from $dest_table where #{cond};", location)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def SQLStatement.for_string(sql, location = nil)
|
|
12
|
+
new(StringResource.new(sql, location))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(resource, declarations = nil)
|
|
16
|
+
@resource = resource
|
|
17
|
+
@declarations = declarations
|
|
18
|
+
@code = nil
|
|
19
|
+
@replace = nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr_reader :resource
|
|
23
|
+
|
|
24
|
+
def ==(other)
|
|
25
|
+
return false unless other.kind_of?(SQLStatement)
|
|
26
|
+
@resource == other.resource
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
alias eql? ==
|
|
30
|
+
|
|
31
|
+
def hash
|
|
32
|
+
@resource.hash
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def declarations
|
|
36
|
+
@declarations ||= Declarations.parse(@resource)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_writer :declarations
|
|
40
|
+
|
|
41
|
+
def replace(re, subst)
|
|
42
|
+
@replace = [re, subst]
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def bind(ctx, vars)
|
|
47
|
+
raise FatalError, "already bound SQL statement: #{@resource.name}" if @code
|
|
48
|
+
src = ctx.parameter_file_loader.eruby(@resource.content, @resource.name || '-')
|
|
49
|
+
@code = Variable.expand_string(apply_replace(src)) {|name|
|
|
50
|
+
val = vars[name]
|
|
51
|
+
val.respond_to?(:to_sql) ? val.to_sql : val.to_s
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def location
|
|
56
|
+
@resource.name
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def source
|
|
60
|
+
@code or raise FatalError, "unbound SQL statement: #{@resource.name}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def stripped_source
|
|
64
|
+
strip_sql(source)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def kind
|
|
68
|
+
# This implementation is not complete but useful
|
|
69
|
+
first_word = @resource.content.gsub(%r<'(?:[^']+|'')*'|"(?:[^"]+|"")*"|--.*|/\*(?m:.*?)\*/>, '').strip.slice(/\A\w+/)
|
|
70
|
+
op = first_word ? first_word.downcase : nil
|
|
71
|
+
case op
|
|
72
|
+
when 'with' then 'select'
|
|
73
|
+
else op
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def inspect
|
|
78
|
+
"\#<#{self.class} #{@resource.inspect}>"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def raw_content
|
|
82
|
+
@resource.content
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def stripped_raw_content
|
|
86
|
+
strip_sql(raw_content)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def meta_data
|
|
90
|
+
SQLMetaDataParser.new.parse(raw_content)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def attributes
|
|
94
|
+
Array(meta_data['attributes'])
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def apply_replace(content)
|
|
100
|
+
return content unless @replace
|
|
101
|
+
re, subst = @replace
|
|
102
|
+
content.gsub(re, subst)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def strip_sql(src)
|
|
106
|
+
src.sub(%r{\A/\*.*?^\*/}m, '').gsub(/^--.*/, '').strip
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Variable declarations in parameter files
|
|
111
|
+
class Declarations
|
|
112
|
+
def Declarations.parse(source)
|
|
113
|
+
SQLMetaDataParser.new.parse_declarations(source)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def initialize(vars = {})
|
|
117
|
+
@vars = {}
|
|
118
|
+
vars.each do |name, default_value|
|
|
119
|
+
declare name, default_value
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def inspect
|
|
124
|
+
"\#<#{self.class} #{@vars.inspect}>"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def declared?(name)
|
|
128
|
+
@vars.key?(name.to_s)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def [](name)
|
|
132
|
+
@vars[name.to_s] or raise ParameterError, "undeclared variable: #{name}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def each(&block)
|
|
136
|
+
@vars.each_value(&block)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def default_variables
|
|
140
|
+
Variables.define {|vars|
|
|
141
|
+
each do |decl|
|
|
142
|
+
if decl.have_default_value?
|
|
143
|
+
vars[decl.name.to_s] = decl.default_value
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
}
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def freeze
|
|
150
|
+
@vars.freeze
|
|
151
|
+
super
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def declare(name, value)
|
|
155
|
+
raise ParameterError, "duplicated variable declaration: #{name}" if declared?(name)
|
|
156
|
+
varname = name.to_s.tr('-', '_')
|
|
157
|
+
@vars[varname] = Declaration.new(varname, value)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def declare_table(name, value)
|
|
161
|
+
case value
|
|
162
|
+
when String
|
|
163
|
+
declare name, value
|
|
164
|
+
when nil
|
|
165
|
+
declare name, name
|
|
166
|
+
else
|
|
167
|
+
raise ParameterError, "bad default value for table variable: #{value.class} (#{value.inspect})"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def declare_tables(label, value)
|
|
172
|
+
case value
|
|
173
|
+
when String
|
|
174
|
+
declare value, value
|
|
175
|
+
when Array
|
|
176
|
+
value.each do |name|
|
|
177
|
+
declare name, name
|
|
178
|
+
end
|
|
179
|
+
when Hash
|
|
180
|
+
value.each do |name, val|
|
|
181
|
+
declare name, val
|
|
182
|
+
end
|
|
183
|
+
else
|
|
184
|
+
raise ParameterError, "'#{label}' entry value must be a string, a list or a map but a #{value.class}"
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def declare_map(label, value)
|
|
189
|
+
case value
|
|
190
|
+
when String
|
|
191
|
+
declare value, nil
|
|
192
|
+
when Array
|
|
193
|
+
value.each do |name|
|
|
194
|
+
declare name, nil
|
|
195
|
+
end
|
|
196
|
+
when Hash
|
|
197
|
+
value.each do |name, val|
|
|
198
|
+
declare name, val
|
|
199
|
+
end
|
|
200
|
+
else
|
|
201
|
+
raise ParameterError, "'#{label}' entry value must be a string, a list or a map but a #{value.class}"
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def declare_none(label, value)
|
|
206
|
+
# ignore
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Accepts all variable as declared dynamically
|
|
211
|
+
class DynamicDeclarations
|
|
212
|
+
def declared?(name)
|
|
213
|
+
true
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def each
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def [](name)
|
|
220
|
+
Declaration.new(name.to_s, nil)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
class Declaration
|
|
225
|
+
def initialize(name, default_value)
|
|
226
|
+
@name = name
|
|
227
|
+
@default_value = default_value
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
attr_reader :name
|
|
231
|
+
attr_reader :default_value
|
|
232
|
+
|
|
233
|
+
def have_default_value?
|
|
234
|
+
not @default_value.nil?
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def inspect
|
|
238
|
+
"\#<#{self.class} #{@name}#{have_default_value? ? '=' + @default_value.inspect : ''}>"
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
class SQLMetaDataParser
|
|
243
|
+
def parse_declarations(source)
|
|
244
|
+
build_decls(parse(source))
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def parse(source)
|
|
248
|
+
parse_meta(read_mata(source))
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
private
|
|
252
|
+
|
|
253
|
+
DECLARATIONS = {
|
|
254
|
+
'dest-table' => 'table',
|
|
255
|
+
'src-tables' => 'tables',
|
|
256
|
+
'params' => 'map',
|
|
257
|
+
'attributes' => 'none'
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
def build_decls(map)
|
|
261
|
+
decls = Declarations.new
|
|
262
|
+
DECLARATIONS.each do |key, type|
|
|
263
|
+
value = map.delete(key)
|
|
264
|
+
decls.send "declare_#{type}", key, value if value
|
|
265
|
+
end
|
|
266
|
+
unless map.empty?
|
|
267
|
+
raise ParameterError, "unknown SQL parameter declaration: #{map.keys.join(', ')}"
|
|
268
|
+
end
|
|
269
|
+
decls.freeze
|
|
270
|
+
decls
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def parse_meta(source)
|
|
274
|
+
return Hash.new if source.empty?
|
|
275
|
+
YAML.load(source) || {}
|
|
276
|
+
rescue YAML::SyntaxError => ex
|
|
277
|
+
raise ParameterError, "SQL meta data syntax error: #{ex.message}"
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
KEYS_RE = /\A--(?:#{DECLARATIONS.keys.join('|')}):/
|
|
281
|
+
|
|
282
|
+
def read_mata(source)
|
|
283
|
+
lines = ''
|
|
284
|
+
source.each_line do |line|
|
|
285
|
+
case line
|
|
286
|
+
when KEYS_RE
|
|
287
|
+
lines.concat line.sub(/\A--/, '')
|
|
288
|
+
when /\A--([\w\-]+):/
|
|
289
|
+
key = $1
|
|
290
|
+
raise ParameterError, "unknown SQL meta data: #{source.name}: #{key}"
|
|
291
|
+
when /\A--/
|
|
292
|
+
# skip non-metadata comments
|
|
293
|
+
lines.concat "\n"
|
|
294
|
+
else
|
|
295
|
+
break
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
lines
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
class TableSpec
|
|
303
|
+
# "[SCHEMA.]TABLE" -> TableSpec(SCHEMA, TABLE)
|
|
304
|
+
def TableSpec.parse(spec)
|
|
305
|
+
new(*split_name(spec))
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# "TABLE" -> [nil, "TABLE"]
|
|
309
|
+
# "SCHEMA.TABLE" -> ["SCHEMA", "TABLE"]
|
|
310
|
+
def TableSpec.split_name(name_pair)
|
|
311
|
+
raise ParameterError, "table spec is empty" if name_pair.strip.empty?
|
|
312
|
+
components = name_pair.split('.', 2)
|
|
313
|
+
if components.size == 1
|
|
314
|
+
return nil, components.first
|
|
315
|
+
else
|
|
316
|
+
s, t = components
|
|
317
|
+
raise ParameterError, "schema name is blank" if not s or s.empty?
|
|
318
|
+
raise ParameterError, "table name is blank" if not t or t.empty?
|
|
319
|
+
return s, t
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def initialize(schema, name)
|
|
324
|
+
@schema = schema
|
|
325
|
+
@name = name
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
attr_reader :schema
|
|
329
|
+
attr_reader :name
|
|
330
|
+
|
|
331
|
+
def to_s
|
|
332
|
+
@schema ? "#{@schema}.#{@name}" : @name
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def inspect
|
|
336
|
+
"\#<#{self.class} #{to_s}>"
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def ==(other)
|
|
340
|
+
return false unless other.kind_of?(TableSpec)
|
|
341
|
+
@schema == other.schema and @name == other.name
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
alias eql? ==
|
|
345
|
+
|
|
346
|
+
def hash
|
|
347
|
+
@schema.hash ^ @name.hash
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
require 'bricolage/jobflow'
|
|
2
|
+
require 'bricolage/exception'
|
|
3
|
+
require 'pathname'
|
|
4
|
+
|
|
5
|
+
module Bricolage
|
|
6
|
+
|
|
7
|
+
class TaskQueue
|
|
8
|
+
def initialize
|
|
9
|
+
@queue = []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def empty?
|
|
13
|
+
@queue.empty?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def size
|
|
17
|
+
@queue.size
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def queued?
|
|
21
|
+
not empty?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def each(&block)
|
|
25
|
+
@queue.each(&block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def consume_each
|
|
29
|
+
lock
|
|
30
|
+
save
|
|
31
|
+
while task = self.next
|
|
32
|
+
yield task
|
|
33
|
+
deq
|
|
34
|
+
end
|
|
35
|
+
ensure
|
|
36
|
+
unlock
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def enq(task)
|
|
40
|
+
@queue.push task
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def next
|
|
44
|
+
@queue.first
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def deq
|
|
48
|
+
task = @queue.shift
|
|
49
|
+
save
|
|
50
|
+
task
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def save
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def restore
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def locked?
|
|
60
|
+
false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def lock
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def unlock
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def unlock_help
|
|
70
|
+
"[MUST NOT HAPPEN] this message must not be shown"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class FileTaskQueue < TaskQueue
|
|
75
|
+
def FileTaskQueue.restore_if_exist(path)
|
|
76
|
+
q = new(path)
|
|
77
|
+
q.restore if q.queued?
|
|
78
|
+
q
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def initialize(path)
|
|
82
|
+
super()
|
|
83
|
+
@path = path
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def queued?
|
|
87
|
+
@path.exist?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def save
|
|
91
|
+
if empty?
|
|
92
|
+
@path.unlink if @path.exist?
|
|
93
|
+
return
|
|
94
|
+
end
|
|
95
|
+
tmpname = "#{@path}.tmp.#{Process.pid}"
|
|
96
|
+
begin
|
|
97
|
+
File.open(tmpname, 'w') {|f|
|
|
98
|
+
each do |task|
|
|
99
|
+
f.puts task.serialize
|
|
100
|
+
end
|
|
101
|
+
}
|
|
102
|
+
File.rename tmpname, @path
|
|
103
|
+
ensure
|
|
104
|
+
FileUtils.rm_f tmpname
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def restore
|
|
109
|
+
File.foreach(@path) do |line|
|
|
110
|
+
enq JobTask.deserialize(line)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def locked?
|
|
115
|
+
lock_file_path.exist?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def lock
|
|
119
|
+
FileUtils.touch lock_file_path
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def unlock
|
|
123
|
+
FileUtils.rm_f lock_file_path
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def lock_file_path
|
|
127
|
+
Pathname.new("#{@path}.LOCK")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def unlock_help
|
|
131
|
+
"remove the file: #{lock_file_path}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
class JobTask
|
|
136
|
+
def initialize(jobnet, seq, job)
|
|
137
|
+
@jobnet = jobnet
|
|
138
|
+
@seq = seq
|
|
139
|
+
@job = job
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
attr_reader :jobnet
|
|
143
|
+
attr_reader :seq
|
|
144
|
+
attr_reader :job
|
|
145
|
+
|
|
146
|
+
def serialize
|
|
147
|
+
[@jobnet, @seq, @job].join("\t")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def JobTask.deserialize(str)
|
|
151
|
+
jobnet, seq, job = str.strip.split("\t", 3)
|
|
152
|
+
new(JobFlow::Ref.parse(jobnet), seq.to_i, JobFlow::Ref.parse(job))
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
end
|