nodectl 0.2.4
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/.gitignore +18 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +4 -0
- data/README.md +3 -0
- data/Rakefile +1 -0
- data/TODO.md +7 -0
- data/assets/javascript/application.coffee +22 -0
- data/assets/javascript/bootstrap.js +2006 -0
- data/assets/javascript/controllers/application_controller.coffee +73 -0
- data/assets/javascript/controllers/service_controller.coffee +62 -0
- data/assets/javascript/event_handler.coffee +51 -0
- data/assets/javascript/models/action.coffee +28 -0
- data/assets/javascript/models/instance.coffee +7 -0
- data/assets/javascript/models/service.coffee +75 -0
- data/assets/javascript/models/version.coffee +3 -0
- data/assets/javascript/notifier.coffee +64 -0
- data/assets/javascript/router.coffee +3 -0
- data/assets/javascript/routes/application.coffee +6 -0
- data/assets/javascript/routes/log.coffee +4 -0
- data/assets/javascript/routes/service.coffee +3 -0
- data/assets/javascript/store.coffee +32 -0
- data/assets/javascript/templates/application.hbs +82 -0
- data/assets/javascript/templates/index.hbs +1 -0
- data/assets/javascript/templates/log.hbs +1 -0
- data/assets/javascript/templates/service.hbs +110 -0
- data/assets/javascript/templates/views/action.hbs +20 -0
- data/assets/javascript/templates/views/actions_list.hbs +26 -0
- data/assets/javascript/templates/views/environments_list.hbs +9 -0
- data/assets/javascript/templates/views/install_button.hbs +1 -0
- data/assets/javascript/templates/views/mass_run.hbs +30 -0
- data/assets/javascript/templates/views/notifier_control.hbs +7 -0
- data/assets/javascript/templates/views/param.hbs +6 -0
- data/assets/javascript/templates/views/terminal.hbs +8 -0
- data/assets/javascript/ui.coffee +24 -0
- data/assets/javascript/vendor/ansiparse.js +186 -0
- data/assets/javascript/vendor/audio.min.js +24 -0
- data/assets/javascript/vendor/audiojs.swf +0 -0
- data/assets/javascript/vendor/ember-data.js +10528 -0
- data/assets/javascript/vendor/ember.js +40583 -0
- data/assets/javascript/vendor/handlebars.js +2746 -0
- data/assets/javascript/vendor/jquery.js +8829 -0
- data/assets/javascript/vendor/ladda.js +157 -0
- data/assets/javascript/vendor/moment.min.js +6 -0
- data/assets/javascript/vendor/notify-osd.js +319 -0
- data/assets/javascript/vendor/player-graphics.gif +0 -0
- data/assets/javascript/vendor/spin.js +218 -0
- data/assets/javascript/views/action_view.coffee +18 -0
- data/assets/javascript/views/actions_list_view.coffee +2 -0
- data/assets/javascript/views/environments_list_view.coffee +2 -0
- data/assets/javascript/views/install_button_view.coffee +34 -0
- data/assets/javascript/views/mass_run_view.coffee +107 -0
- data/assets/javascript/views/notifier_control_view.coffee +29 -0
- data/assets/javascript/views/terminal_view.coffee +56 -0
- data/assets/stylesheets/application.css +137 -0
- data/assets/stylesheets/bootstrap-theme.css +347 -0
- data/assets/stylesheets/bootstrap.css +5785 -0
- data/assets/stylesheets/fonts/glyphicons-halflings-regular.eot +0 -0
- data/assets/stylesheets/fonts/glyphicons-halflings-regular.svg +229 -0
- data/assets/stylesheets/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/assets/stylesheets/fonts/glyphicons-halflings-regular.woff +0 -0
- data/assets/stylesheets/ladda-themeless.css +330 -0
- data/assets/stylesheets/ladda.css +392 -0
- data/assets/stylesheets/notify-osd.css +49 -0
- data/assets/stylesheets/terminal.css +12 -0
- data/bin/nodectl +4 -0
- data/lib/nodectl.rb +137 -0
- data/lib/nodectl/action.rb +80 -0
- data/lib/nodectl/binding.rb +13 -0
- data/lib/nodectl/cli.rb +123 -0
- data/lib/nodectl/context.rb +34 -0
- data/lib/nodectl/database.rb +55 -0
- data/lib/nodectl/generators/init.rb +22 -0
- data/lib/nodectl/generators/templates/init/.gitignore +5 -0
- data/lib/nodectl/generators/templates/init/config/manifest.yml +1 -0
- data/lib/nodectl/generators/templates/init/config/server.yml +7 -0
- data/lib/nodectl/instance.rb +100 -0
- data/lib/nodectl/log.rb +13 -0
- data/lib/nodectl/manager.rb +71 -0
- data/lib/nodectl/multi_io.rb +16 -0
- data/lib/nodectl/options.rb +47 -0
- data/lib/nodectl/process.rb +145 -0
- data/lib/nodectl/promised_file.rb +37 -0
- data/lib/nodectl/recipe.rb +197 -0
- data/lib/nodectl/repository.rb +80 -0
- data/lib/nodectl/server.rb +103 -0
- data/lib/nodectl/service.rb +126 -0
- data/lib/nodectl/stream/buffer.rb +44 -0
- data/lib/nodectl/stream/events_session.rb +39 -0
- data/lib/nodectl/stream/file.rb +69 -0
- data/lib/nodectl/stream/file_session.rb +35 -0
- data/lib/nodectl/stream/file_with_memory.rb +17 -0
- data/lib/nodectl/stream/websocket.rb +90 -0
- data/lib/nodectl/version.rb +3 -0
- data/lib/nodectl/watchdog.rb +17 -0
- data/lib/nodectl/webapp/actions.rb +21 -0
- data/lib/nodectl/webapp/base.rb +29 -0
- data/lib/nodectl/webapp/instances.rb +46 -0
- data/lib/nodectl/webapp/public/sounds/alert.mp3 +0 -0
- data/lib/nodectl/webapp/public/sounds/error.mp3 +0 -0
- data/lib/nodectl/webapp/public/sounds/question.mp3 +0 -0
- data/lib/nodectl/webapp/services.rb +39 -0
- data/lib/nodectl/webapp/settings.rb +7 -0
- data/lib/nodectl/webapp/ui.rb +19 -0
- data/lib/nodectl/webapp/versions.rb +12 -0
- data/lib/nodectl/webapp/views/ui.haml +20 -0
- data/nodectl.gemspec +35 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/stream/buffer_spec.rb +33 -0
- metadata +365 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class Nodectl::Generators::Init < Thor::Group
|
|
2
|
+
include Thor::Actions
|
|
3
|
+
|
|
4
|
+
argument :host_path
|
|
5
|
+
|
|
6
|
+
def self.source_root
|
|
7
|
+
File.join(File.dirname(__FILE__), "templates", "init")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.destination_root
|
|
11
|
+
host_path
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def copy_template
|
|
15
|
+
directory "./", host_path
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def database
|
|
19
|
+
db = Nodectl::Database.new(File.join(host_path, "nodectl.db"))
|
|
20
|
+
db.reset
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Write service definitions here
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
class Nodectl::Instance < Nodectl::Process
|
|
2
|
+
@manager = Nodectl::Manager.new(self)
|
|
3
|
+
|
|
4
|
+
attr_reader :service
|
|
5
|
+
attr_reader :options
|
|
6
|
+
attr_reader :command
|
|
7
|
+
|
|
8
|
+
def initialize(service, command, options = {}, &blk)
|
|
9
|
+
super(options) do
|
|
10
|
+
blk.call if blk
|
|
11
|
+
Process.exec command
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
@service = service
|
|
15
|
+
@options = options
|
|
16
|
+
@command = command
|
|
17
|
+
|
|
18
|
+
if @options[:started]
|
|
19
|
+
@status = :running
|
|
20
|
+
@pid = @options[:pid]
|
|
21
|
+
@detach = Process.detach(@pid)
|
|
22
|
+
|
|
23
|
+
Nodectl::Instance[@pid] = self
|
|
24
|
+
Nodectl.logger.info("#{@service.name} [#{@pid}] found")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
onkill do
|
|
28
|
+
self.class.delete(pid, reason: @status)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def run
|
|
33
|
+
super
|
|
34
|
+
|
|
35
|
+
Nodectl::Instance[@pid] = self
|
|
36
|
+
Nodectl.logger.info("#{@service.name} [#{@pid}] started")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def as_json
|
|
40
|
+
{
|
|
41
|
+
"id" => pid,
|
|
42
|
+
"service_id" => service.name,
|
|
43
|
+
"status" => status,
|
|
44
|
+
"options" => options
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def dump
|
|
49
|
+
{
|
|
50
|
+
"service_name" => service.name,
|
|
51
|
+
"pid" => pid,
|
|
52
|
+
"options" => options,
|
|
53
|
+
"command" => command
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def pre_fork_block
|
|
58
|
+
Nodectl.shut_up!
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class << self
|
|
62
|
+
def load(dump)
|
|
63
|
+
service = Nodectl::Service.find(dump["service_name"])
|
|
64
|
+
|
|
65
|
+
if service
|
|
66
|
+
puts "Service found: #{dump['service_name']}"
|
|
67
|
+
new(service, dump["command"], dump["options"].merge(started: true, pid: dump["pid"]))
|
|
68
|
+
else
|
|
69
|
+
puts "Service not found"
|
|
70
|
+
raise "service not found"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def push(pid, instance)
|
|
75
|
+
super(pid, instance)
|
|
76
|
+
save_register
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
alias :[]= :push
|
|
80
|
+
|
|
81
|
+
def delete(pid, options = {})
|
|
82
|
+
super(pid, options)
|
|
83
|
+
save_register
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def save_register
|
|
87
|
+
Nodectl.database["instances"] = all.map(&:dump)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def load_register
|
|
91
|
+
database = Nodectl.database["instances"] || []
|
|
92
|
+
|
|
93
|
+
database.each do |dump|
|
|
94
|
+
load(dump) rescue RuntimeError
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
save_register
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
data/lib/nodectl/log.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
class Nodectl::Manager
|
|
2
|
+
MANAGER_METHODS = [:all, :find, :find!, :push, :delete, :[], :[]=, :next_id, :onadd, :ondelete]
|
|
3
|
+
|
|
4
|
+
def initialize(target_class)
|
|
5
|
+
@objects = {}
|
|
6
|
+
@target_class = target_class
|
|
7
|
+
|
|
8
|
+
@onadd = []
|
|
9
|
+
@ondelete = []
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
MANAGER_METHODS.each do |method|
|
|
13
|
+
@target_class.define_singleton_method method do |*args, &blk|
|
|
14
|
+
@manager.send(method, *args, &blk)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def find(key)
|
|
20
|
+
@objects[key]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
alias :[] :find
|
|
24
|
+
|
|
25
|
+
def find!(key)
|
|
26
|
+
object = find(key)
|
|
27
|
+
|
|
28
|
+
unless object
|
|
29
|
+
raise Nodectl::NotFound, "#{@target_class.name} with key '#{key}' not found"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
object
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def all
|
|
36
|
+
@objects.values
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def push(key, object)
|
|
40
|
+
@objects[key] = object
|
|
41
|
+
onadd_call(object)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
alias :[]= :push
|
|
45
|
+
|
|
46
|
+
def delete(key, options = {})
|
|
47
|
+
object = @objects.delete(key)
|
|
48
|
+
ondelete_call(object, options)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def next_id
|
|
52
|
+
@next_id ||= 0
|
|
53
|
+
@next_id += 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def onadd(&blk)
|
|
57
|
+
@onadd << blk
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def ondelete(&blk)
|
|
61
|
+
@ondelete << blk
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def onadd_call(object)
|
|
65
|
+
@onadd.each { |b| b.call(object) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def ondelete_call(object, options)
|
|
69
|
+
@ondelete.each { |b| b.call(object, options) }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Redirect `write` calls to multiple targets, used with standard ruby
|
|
2
|
+
# logger instance for writing messages to log file and stdout at the
|
|
3
|
+
# same time
|
|
4
|
+
class Nodectl::MultiIO
|
|
5
|
+
def initialize(*targets)
|
|
6
|
+
@targets = targets
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def write(*args)
|
|
10
|
+
@targets.each {|t| t.write(*args)}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def close
|
|
14
|
+
@targets.each(&:close)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Boot options container, keep info about node directory structure etc
|
|
2
|
+
class Nodectl::Options
|
|
3
|
+
def initialize(overrides = {})
|
|
4
|
+
@options = default_options.merge(overrides)
|
|
5
|
+
@options = calculate_options(@options)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def [](option_name)
|
|
9
|
+
@options[option_name.to_s]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def inspect
|
|
13
|
+
@options.inspect
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s
|
|
17
|
+
@options.to_s
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_hash
|
|
21
|
+
@options
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def default_options
|
|
27
|
+
{
|
|
28
|
+
"root" => Dir.pwd,
|
|
29
|
+
"log_level" => "INFO"
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def calculate_options(source_options)
|
|
34
|
+
options = source_options.dup
|
|
35
|
+
|
|
36
|
+
options["root"] = Pathname.new(options["root"])
|
|
37
|
+
|
|
38
|
+
options["config_dir"] ||= options["root"].join("config")
|
|
39
|
+
options["source_dir"] ||= options["root"].join("src")
|
|
40
|
+
options["logger_dir"] ||= options["root"].join("log")
|
|
41
|
+
options["recipe_dir"] ||= options["root"].join("recipes")
|
|
42
|
+
options["database"] ||= options["root"].join("nodectl.db")
|
|
43
|
+
options["manifest"] ||= options["config_dir"].join("manifest.yml")
|
|
44
|
+
|
|
45
|
+
options
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
class Nodectl::Process
|
|
2
|
+
ENV_BLACKLIST = %w(BUNDLE_BIN_PATH BUNDLE_GEMFILE USE_BUNDLER _ORIGINAL_GEM_PATH _ RUBYOPT)
|
|
3
|
+
TIMEOUT = 10
|
|
4
|
+
|
|
5
|
+
@manager = Nodectl::Manager.new(self)
|
|
6
|
+
|
|
7
|
+
attr_reader :pid
|
|
8
|
+
attr_reader :status
|
|
9
|
+
attr_reader :exit_status
|
|
10
|
+
|
|
11
|
+
def initialize(options = {}, &blk)
|
|
12
|
+
raise "cannot create process without block" unless block_given?
|
|
13
|
+
|
|
14
|
+
@status = :pending
|
|
15
|
+
@stopping = false
|
|
16
|
+
@options = options
|
|
17
|
+
@blk = blk
|
|
18
|
+
@oncrash = []
|
|
19
|
+
@onstop = []
|
|
20
|
+
@onkill = []
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def run
|
|
24
|
+
if @status != :pending
|
|
25
|
+
raise "Cannot run one process twice"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@pid = fork do
|
|
29
|
+
ENV_BLACKLIST.each do |name|
|
|
30
|
+
ENV.delete(name)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
args = @options[:args] || []
|
|
34
|
+
@blk.call(*args)
|
|
35
|
+
|
|
36
|
+
EM.stop
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@detach = Process.detach(@pid)
|
|
40
|
+
|
|
41
|
+
@status = :running
|
|
42
|
+
Nodectl::Process[pid] = self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def alive?
|
|
46
|
+
Process.getpgid(pid)
|
|
47
|
+
true
|
|
48
|
+
rescue Errno::ESRCH
|
|
49
|
+
dead!
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def stop(options = {})
|
|
54
|
+
@status = :stopping
|
|
55
|
+
signal = options[:force] ? "KILL" : "TERM"
|
|
56
|
+
|
|
57
|
+
Nodectl.logger.info("process: send #{signal} to #{pid}")
|
|
58
|
+
|
|
59
|
+
Process.kill(signal, pid)
|
|
60
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
61
|
+
dead!
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def restart(options = {})
|
|
65
|
+
stop(options)
|
|
66
|
+
TIMEOUT.times do
|
|
67
|
+
unless alive?
|
|
68
|
+
@status = :pending
|
|
69
|
+
run
|
|
70
|
+
break
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
sleep 1
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def dead!(exit_status = nil)
|
|
78
|
+
@detach.join
|
|
79
|
+
|
|
80
|
+
if @detach.value
|
|
81
|
+
@exit_status = @detach.value.exitstatus
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if [:running, :stopping].include?(@status)
|
|
85
|
+
case @status
|
|
86
|
+
when :stopping
|
|
87
|
+
@status = :stopped
|
|
88
|
+
Nodectl.logger.info("process: [#{pid}] stopped")
|
|
89
|
+
onstop_call
|
|
90
|
+
when :running
|
|
91
|
+
@status = :crashed
|
|
92
|
+
Nodectl.logger.info("process: [#{pid}] crashed")
|
|
93
|
+
oncrash_call
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
onkill_call
|
|
97
|
+
Nodectl::Process.delete(pid)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# When process crashed
|
|
102
|
+
def oncrash(&blk)
|
|
103
|
+
@oncrash << blk
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# When process gracefully stoped
|
|
107
|
+
def onstop(&blk)
|
|
108
|
+
@onstop << blk
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# When process is dead, regardless of reason
|
|
112
|
+
def onkill(&blk)
|
|
113
|
+
@onkill << blk
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
def oncrash_call
|
|
119
|
+
@oncrash.each { |b| b.call(self) }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def onkill_call
|
|
123
|
+
@onkill.each { |b| b.call(self) }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def onstop_call
|
|
127
|
+
@onstop.each { |b| b.call(self) }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def pre_fork_block
|
|
131
|
+
# Override it to customize process behaviour
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def post_fork_block
|
|
135
|
+
# Override it to customize process behaviour
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def fork(&blk)
|
|
139
|
+
Process.fork do
|
|
140
|
+
pre_fork_block
|
|
141
|
+
blk.call
|
|
142
|
+
post_fork_block
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Promised file is file which looks like an empty file while it does not exists
|
|
2
|
+
class Nodectl::PromisedFile
|
|
3
|
+
def initialize(path, file_class, options = {})
|
|
4
|
+
@path = path
|
|
5
|
+
@file_class = file_class
|
|
6
|
+
@options = options
|
|
7
|
+
@fake_file = Nodectl::Stream::File.new(StringIO.new)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def close
|
|
11
|
+
@closed = true
|
|
12
|
+
current_file.close
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Hook Nodectl::Stream::File#onread for call it when real
|
|
16
|
+
# file was created
|
|
17
|
+
def onread(&blk)
|
|
18
|
+
@onread = blk
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def method_missing(name, *args, &blk)
|
|
22
|
+
current_file.public_send(name, *args, &blk)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def current_file
|
|
28
|
+
if !@real_file && !@closed && File.exist?(@path)
|
|
29
|
+
@real_file = @file_class.new(@path, @options)
|
|
30
|
+
@real_file.onread(&@onread) if @onread
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@real_file || @fake_file
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
end
|