kanrisuru 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +17 -0
  3. data/.rubocop.yml +47 -0
  4. data/.rubocop_todo.yml +0 -0
  5. data/CHANGELOG.md +0 -0
  6. data/Gemfile +2 -5
  7. data/LICENSE.txt +1 -1
  8. data/README.md +143 -7
  9. data/Rakefile +5 -3
  10. data/bin/console +4 -3
  11. data/kanrisuru.gemspec +21 -12
  12. data/lib/kanrisuru.rb +41 -2
  13. data/lib/kanrisuru/command.rb +99 -0
  14. data/lib/kanrisuru/core.rb +53 -0
  15. data/lib/kanrisuru/core/archive.rb +154 -0
  16. data/lib/kanrisuru/core/disk.rb +302 -0
  17. data/lib/kanrisuru/core/file.rb +332 -0
  18. data/lib/kanrisuru/core/find.rb +108 -0
  19. data/lib/kanrisuru/core/group.rb +97 -0
  20. data/lib/kanrisuru/core/ip.rb +1032 -0
  21. data/lib/kanrisuru/core/mount.rb +138 -0
  22. data/lib/kanrisuru/core/path.rb +140 -0
  23. data/lib/kanrisuru/core/socket.rb +168 -0
  24. data/lib/kanrisuru/core/stat.rb +104 -0
  25. data/lib/kanrisuru/core/stream.rb +121 -0
  26. data/lib/kanrisuru/core/system.rb +348 -0
  27. data/lib/kanrisuru/core/transfer.rb +203 -0
  28. data/lib/kanrisuru/core/user.rb +198 -0
  29. data/lib/kanrisuru/logger.rb +8 -0
  30. data/lib/kanrisuru/mode.rb +277 -0
  31. data/lib/kanrisuru/os_package.rb +235 -0
  32. data/lib/kanrisuru/remote.rb +10 -0
  33. data/lib/kanrisuru/remote/cluster.rb +95 -0
  34. data/lib/kanrisuru/remote/cpu.rb +68 -0
  35. data/lib/kanrisuru/remote/env.rb +33 -0
  36. data/lib/kanrisuru/remote/file.rb +354 -0
  37. data/lib/kanrisuru/remote/fstab.rb +412 -0
  38. data/lib/kanrisuru/remote/host.rb +191 -0
  39. data/lib/kanrisuru/remote/memory.rb +19 -0
  40. data/lib/kanrisuru/remote/os.rb +87 -0
  41. data/lib/kanrisuru/result.rb +78 -0
  42. data/lib/kanrisuru/template.rb +32 -0
  43. data/lib/kanrisuru/util.rb +40 -0
  44. data/lib/kanrisuru/util/bits.rb +203 -0
  45. data/lib/kanrisuru/util/fs_mount_opts.rb +655 -0
  46. data/lib/kanrisuru/util/os_family.rb +213 -0
  47. data/lib/kanrisuru/util/signal.rb +161 -0
  48. data/lib/kanrisuru/version.rb +3 -1
  49. data/spec/functional/core/archive_spec.rb +228 -0
  50. data/spec/functional/core/disk_spec.rb +80 -0
  51. data/spec/functional/core/file_spec.rb +341 -0
  52. data/spec/functional/core/find_spec.rb +52 -0
  53. data/spec/functional/core/group_spec.rb +65 -0
  54. data/spec/functional/core/ip_spec.rb +71 -0
  55. data/spec/functional/core/path_spec.rb +93 -0
  56. data/spec/functional/core/socket_spec.rb +31 -0
  57. data/spec/functional/core/stat_spec.rb +98 -0
  58. data/spec/functional/core/stream_spec.rb +99 -0
  59. data/spec/functional/core/system_spec.rb +96 -0
  60. data/spec/functional/core/transfer_spec.rb +108 -0
  61. data/spec/functional/core/user_spec.rb +76 -0
  62. data/spec/functional/os_package_spec.rb +75 -0
  63. data/spec/functional/remote/cluster_spec.rb +45 -0
  64. data/spec/functional/remote/cpu_spec.rb +41 -0
  65. data/spec/functional/remote/env_spec.rb +36 -0
  66. data/spec/functional/remote/fstab_spec.rb +76 -0
  67. data/spec/functional/remote/host_spec.rb +68 -0
  68. data/spec/functional/remote/memory_spec.rb +29 -0
  69. data/spec/functional/remote/os_spec.rb +63 -0
  70. data/spec/functional/remote/remote_file_spec.rb +180 -0
  71. data/spec/helper/test_hosts.rb +68 -0
  72. data/spec/hosts.json +92 -0
  73. data/spec/spec_helper.rb +11 -3
  74. data/spec/unit/fstab_spec.rb +22 -0
  75. data/spec/unit/kanrisuru_spec.rb +9 -0
  76. data/spec/unit/mode_spec.rb +183 -0
  77. data/spec/unit/template_spec.rb +13 -0
  78. data/spec/unit/util_spec.rb +177 -0
  79. data/spec/zz_reboot_spec.rb +46 -0
  80. metadata +136 -13
  81. data/spec/kanrisuru_spec.rb +0 -9
@@ -0,0 +1,235 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Kanrisuru
6
+ module OsPackage
7
+ module Define
8
+ def self.extended(base)
9
+ base.instance_variable_set(:@os_method_properties, {})
10
+ base.instance_variable_set(:@os_methods, Set.new)
11
+ base.instance_variable_set(:@os_method_cache, {})
12
+ end
13
+
14
+ def os_define(os_name, method_name, options = {})
15
+ unique_method_name = options[:alias] || method_name
16
+
17
+ @os_methods.add(method_name)
18
+
19
+ if @os_method_properties.key?(unique_method_name)
20
+ params = {
21
+ os_name: os_name.to_s,
22
+ method_name: method_name,
23
+ options: options
24
+ }
25
+
26
+ @os_method_properties[unique_method_name].prepend(params)
27
+ else
28
+ @os_method_properties[unique_method_name] = [{
29
+ os_name: os_name.to_s,
30
+ method_name: method_name,
31
+ options: options
32
+ }]
33
+ end
34
+ end
35
+ end
36
+
37
+ module Collection
38
+ def os_collection(mod, opts = {})
39
+ os_method_properties = mod.instance_variable_get(:@os_method_properties)
40
+ os_method_names = os_method_properties.keys
41
+
42
+ namespace = opts[:namespace]
43
+ namespace_instance = nil
44
+
45
+ if namespace
46
+ ## Define the namespace as an eigen class instance on the host.
47
+ ## Namespaced instances will access core host methods
48
+ ## with @host instance variable.
49
+ namespace_class = Kanrisuru::Remote::Cluster.const_set(Kanrisuru::Util.camelize(namespace), Class.new)
50
+ namespace_instance = Kanrisuru::Remote::Cluster.instance_variable_set("@#{namespace}", namespace_class.new)
51
+
52
+ class_eval do
53
+ define_method namespace do
54
+ namespace_instance.instance_variable_set(:@cluster, self)
55
+ namespace_instance
56
+ end
57
+ end
58
+
59
+ namespace_class.class_eval do
60
+ os_method_names.each do |method_name|
61
+ define_method method_name do |*args, &block|
62
+ cluster = namespace_instance.instance_variable_get(:@cluster)
63
+ hosts = cluster.instance_variable_get(:@hosts)
64
+ hosts.map do |host_addr, host|
65
+ { host: host_addr, result: host.send(namespace).send(method_name, *args, &block) }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ else
71
+ class_eval do
72
+ os_method_names.each do |method_name|
73
+ define_method method_name do |*args, &block|
74
+ @hosts.map do |host_addr, host|
75
+ { host: host_addr, result: host.send(method_name, *args, &block) }
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ module Include
85
+ def os_include(mod, opts = {})
86
+ os_method_properties = mod.instance_variable_get(:@os_method_properties)
87
+ os_method_names = os_method_properties.keys
88
+
89
+ ## Need to encapsulate any helper methods called in the module
90
+ ## to bind to the host instance. This acts as a psudeo module include.
91
+ os_methods = mod.instance_variable_get(:@os_methods)
92
+
93
+ public_methods = mod.instance_methods(false) - os_methods.to_a
94
+ private_methods = mod.private_instance_methods(false)
95
+ protected_methods = mod.protected_instance_methods(false)
96
+ include_methods = (public_methods + protected_methods + private_methods).flatten
97
+
98
+ include_method_bindings = proc do
99
+ define_method 'os_method_cache' do
100
+ @os_method_cache ||= {}
101
+ end
102
+
103
+ private :os_method_cache
104
+
105
+ include_methods.each do |method_name|
106
+ define_method method_name do |*args, &block|
107
+ unbound_method = mod.instance_method(method_name)
108
+ bind_method(unbound_method, *args, &block)
109
+ end
110
+ end
111
+
112
+ private_methods.each { |method_name| private(method_name) }
113
+ protected_methods.each { |method_name| protected(method_name) }
114
+
115
+ private
116
+ if RUBY_VERSION < '2.7'
117
+ define_method 'bind_method' do |unbound_method, *args, &block|
118
+ unbound_method.bind(self).call(*args, &block)
119
+ end
120
+ else
121
+ define_method 'bind_method' do |unbound_method, *args, &block|
122
+ unbound_method.bind_call(self, *args, &block)
123
+ end
124
+ end
125
+ end
126
+
127
+ namespace = opts[:namespace]
128
+ namespace_class = nil
129
+ namespace_instance = nil
130
+
131
+ if namespace
132
+ ## Define the namespace as an eigen class instance within the host class.
133
+ ## Namespaced instances will access core host methods
134
+ ## with @host instance variable.
135
+ namespace_class = Kanrisuru::Remote::Host.const_set(Kanrisuru::Util.camelize(namespace), Class.new)
136
+ namespace_instance = Kanrisuru::Remote::Host.instance_variable_set("@#{namespace}", namespace_class.new)
137
+
138
+ namespace_class.class_eval(&include_method_bindings)
139
+
140
+ class_eval do
141
+ define_method namespace do
142
+ namespace_instance.instance_variable_set(:@host, self)
143
+ namespace_instance
144
+ end
145
+ end
146
+ else
147
+ class_eval(&include_method_bindings)
148
+ end
149
+
150
+ class_eval do
151
+ os_method_names.each do |method_name|
152
+ if namespace
153
+ namespace_class.class_eval do
154
+ define_method method_name do |*args, &block|
155
+ unbound_method = nil
156
+
157
+ if os_method_cache.key?(method_name)
158
+ unbound_method = os_method_cache[method_name]
159
+ else
160
+ host = namespace_instance.instance_variable_get(:@host)
161
+
162
+ ## Find the correct method to resolve based on the OS for the remote host.
163
+ defined_method_name = host.resolve_os_method_name(os_method_properties, method_name)
164
+ unless defined_method_name
165
+ raise NoMethodError, "undefined method `#{method_name}' for #{self.class}"
166
+ end
167
+
168
+ ## Get reference to the unbound method defined in module
169
+ unbound_method = mod.instance_method(defined_method_name)
170
+ raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless unbound_method
171
+
172
+ ## Cache the unbound method on this host instance for faster resolution on
173
+ ## the next invocation of this method
174
+ os_method_cache[method_name] = unbound_method
175
+ end
176
+
177
+ ## Bind the method to host instance and
178
+ ## call it with args and block
179
+ bind_method(unbound_method, *args, &block)
180
+ end
181
+ end
182
+ else
183
+ define_method method_name do |*args, &block|
184
+ unbound_method = nil
185
+
186
+ if os_method_cache.key?(method_name)
187
+ unbound_method = os_method_cache[method_name]
188
+ else
189
+ host = self
190
+
191
+ ## Find the correct method to resolve based on the OS for the remote host.
192
+ defined_method_name = host.resolve_os_method_name(os_method_properties, method_name)
193
+ raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless defined_method_name
194
+
195
+ ## Get reference to the unbound method defined in module
196
+ unbound_method = mod.instance_method(defined_method_name)
197
+ raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless unbound_method
198
+
199
+ ## Cache the unbound method on this host instance for faster resolution on
200
+ ## the next invocation of this method
201
+ os_method_cache[method_name] = unbound_method
202
+ end
203
+
204
+ ## Bind the method to host instance and
205
+ ## call it with args and block
206
+ bind_method(unbound_method, *args, &block)
207
+ end
208
+ end
209
+ end
210
+
211
+ def resolve_os_method_name(properties, method_name)
212
+ kernel = os.kernel.downcase
213
+ release = os.release.downcase
214
+
215
+ properties[method_name].each do |property|
216
+ os_name = property[:os_name]
217
+ strict = property[:options] ? property[:options][:strict] : false
218
+ except = property[:options] ? property[:options][:except] : ''
219
+
220
+ next if except && (except == release || except.include?(release))
221
+
222
+ if release == os_name || kernel == os_name ||
223
+ (Kanrisuru::Util::OsFamily.family_include_distribution?(os_name, release) && !strict) ||
224
+ (Kanrisuru::Util::OsFamily.upstream_include_distribution?(os_name, release) && !strict)
225
+ return property[:method_name]
226
+ end
227
+ end
228
+
229
+ nil
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'remote/host'
4
+ require_relative 'remote/cluster'
5
+ require_relative 'remote/os'
6
+ require_relative 'remote/env'
7
+ require_relative 'remote/memory'
8
+ require_relative 'remote/cpu'
9
+ require_relative 'remote/file'
10
+ require_relative 'remote/fstab'
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kanrisuru
4
+ module Remote
5
+ class Cluster
6
+ extend OsPackage::Collection
7
+ include Enumerable
8
+
9
+ def initialize(hosts)
10
+ @hosts = {}
11
+ hosts.each do |host_opts|
12
+ add_host(host_opts)
13
+ end
14
+ end
15
+
16
+ def [](hostname)
17
+ @hosts[hostname]
18
+ end
19
+
20
+ def <<(host_opts)
21
+ add_host(host_opts)
22
+ end
23
+
24
+ def execute(command)
25
+ @hosts.map do |host_addr, host|
26
+ ## Need to evaluate each host independently for the command.
27
+ cmd = Kanrisuru::Command.new(command)
28
+
29
+ { host: host_addr, result: host.execute(cmd) }
30
+ end
31
+ end
32
+
33
+ def execute_shell(command)
34
+ @hosts.map do |host_addr, host|
35
+ ## Need to evaluate each host independently for the command.
36
+ cmd = Kanrisuru::Command.new(command)
37
+
38
+ { host: host_addr, result: host.execute_shell(cmd) }
39
+ end
40
+ end
41
+
42
+ def each(&block)
43
+ @hosts.each { |_host_addr, host| block.call(host) }
44
+ end
45
+
46
+ def hostname
47
+ map_host_results(:hostname)
48
+ end
49
+
50
+ def ping?
51
+ map_host_results(:ping?)
52
+ end
53
+
54
+ def su(user)
55
+ @hosts.each do |_host_addr, host|
56
+ host.su(user)
57
+ end
58
+ end
59
+
60
+ def chdir(path = '~')
61
+ cd(path)
62
+ end
63
+
64
+ def cd(path = '~')
65
+ @hosts.each do |_host_addr, host|
66
+ host.cd(path)
67
+ end
68
+ end
69
+
70
+ def disconnect
71
+ @hosts.each do |_host_addr, host|
72
+ host.disconnect
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def map_host_results(action)
79
+ @hosts.map do |host_addr, host|
80
+ { host: host_addr, result: host.send(action) }
81
+ end
82
+ end
83
+
84
+ def add_host(host_opts)
85
+ if host_opts.instance_of?(Hash)
86
+ @hosts[host_opts[:host]] = Kanrisuru::Remote::Host.new(host_opts)
87
+ elsif host_opts.instance_of?(Kanrisuru::Remote::Host)
88
+ @hosts[host_opts.host] = host_opts
89
+ else
90
+ raise 'Not a valid host option'
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kanrisuru
4
+ module Remote
5
+ class Cpu
6
+ def initialize(host)
7
+ @host = host
8
+
9
+ raise 'Not implemented' unless @host.os && @host.os.kernel == 'Linux'
10
+
11
+ initialize_linux
12
+ end
13
+
14
+ def load_average
15
+ @host.load_average.to_a
16
+ end
17
+
18
+ def load_average1
19
+ load_average[0]
20
+ end
21
+
22
+ def load_average5
23
+ load_average[1]
24
+ end
25
+
26
+ def load_average15
27
+ load_average[2]
28
+ end
29
+
30
+ def sockets
31
+ @sockets_count
32
+ end
33
+
34
+ def cores
35
+ @logical_cores_count
36
+ end
37
+
38
+ def total
39
+ @logical_cores_count
40
+ end
41
+
42
+ def count
43
+ @logical_cores_count
44
+ end
45
+
46
+ def threads_per_core
47
+ @threads_per_core_count
48
+ end
49
+
50
+ def cores_per_socket
51
+ @cores_per_socket_count
52
+ end
53
+
54
+ def hyperthreading?
55
+ threads_per_core > 1
56
+ end
57
+
58
+ private
59
+
60
+ def initialize_linux
61
+ @sockets_count = @host.cpu_info('sockets').to_i
62
+ @cores_per_socket_count = @host.cpu_info('cores_per_socket').to_i
63
+ @threads_per_core_count = @host.cpu_info('threads').to_i
64
+ @logical_cores_count = @host.cpu_info('cores').to_i
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kanrisuru
4
+ module Remote
5
+ class Env
6
+ def initialize
7
+ @env = {}
8
+ end
9
+
10
+ def to_h
11
+ @env
12
+ end
13
+
14
+ def to_s
15
+ string = ''
16
+ @env.each.with_index do |(key, value), index|
17
+ string += "export #{key}=#{value};"
18
+ string += ' ' if index < @env.length - 1
19
+ end
20
+
21
+ string
22
+ end
23
+
24
+ def [](key)
25
+ @env[key.to_s.upcase]
26
+ end
27
+
28
+ def []=(key, value)
29
+ @env[key.to_s.upcase] = value
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,354 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Kanrisuru
6
+ module Remote
7
+ class File
8
+ include Enumerable
9
+
10
+ WRITE_LINE_COUNT = 5_000
11
+ READ_FILE_SIZE = 3_000_000
12
+
13
+ attr_reader :mode, :blocks, :size, :type, :gid, :group, :uid, :user, :inode, :last_access, :last_modified,
14
+ :last_changed, :lines, :words, :chars, :path
15
+
16
+ def initialize(path, host, backup = nil)
17
+ @path = path
18
+ @host = host
19
+ @backup = backup
20
+
21
+ @file_lines = []
22
+
23
+ reload! if exists?
24
+ end
25
+
26
+ def expand_path
27
+ return '' unless exists?
28
+
29
+ result = @host.realpath(@path)
30
+ result.success? ? result.path : ''
31
+ end
32
+
33
+ def exists?
34
+ @host.inode?(@path)
35
+ end
36
+
37
+ def file?
38
+ @host.file?(@path)
39
+ end
40
+
41
+ def [](index)
42
+ if chunk_read_file? && index < @lines
43
+ chunk_read_line_by_index(index)
44
+ elsif index < @lines
45
+ @file_lines[index]
46
+ else
47
+ raise ArgumentError, "Out of bounds index access for #{index}"
48
+ end
49
+ end
50
+
51
+ def dir?
52
+ @host.dir?(@path)
53
+ end
54
+
55
+ def zero?
56
+ @host.empty_file?(@path)
57
+ end
58
+
59
+ def chmod(mode)
60
+ result = @host.chmod(@path, mode)
61
+ update_file_attributes(result) if result.success?
62
+ end
63
+
64
+ def chown(owner, group)
65
+ result = @host.chown(@path, owner: owner, group: group)
66
+ update_file_attributes(result) if result.success?
67
+ end
68
+
69
+ def writeable?
70
+ return @writeable if [true, false].include?(@writeable)
71
+
72
+ if !file? && !zero?
73
+ @writeable = false
74
+ return false
75
+ end
76
+
77
+ current_user = @host.remote_user
78
+ result = @host.get_user(current_user)
79
+
80
+ raise 'Invalid result' unless result.success?
81
+
82
+ groups = result.groups.map(&:name)
83
+
84
+ @writeable = @mode.other.write? ||
85
+ (@mode.group.write? && groups.include?(@group)) ||
86
+ (@mode.owner.write? && @user == current_user)
87
+ end
88
+
89
+ def find_and_replace_value(regex, value)
90
+ new_lines = []
91
+
92
+ each do |existing_line|
93
+ new_lines << if regex.match(existing_line)
94
+ existing_line.gsub(regex, value)
95
+ else
96
+ existing_line
97
+ end
98
+ end
99
+
100
+ write_lines_to_file(new_lines, 'write')
101
+ end
102
+
103
+ def find_and_replace_line(regex, new_line)
104
+ new_lines = []
105
+
106
+ each do |existing_line|
107
+ new_lines << if regex.match(existing_line)
108
+ new_line
109
+ else
110
+ existing_line
111
+ end
112
+ end
113
+
114
+ write_lines_to_file(new_lines, 'write')
115
+ end
116
+
117
+ def find_and_append(regex, new_line)
118
+ new_lines = []
119
+
120
+ each do |existing_line|
121
+ new_lines << existing_line
122
+ new_lines << new_line if regex.match(existing_line)
123
+ end
124
+
125
+ write_lines_to_file(new_lines, 'write')
126
+ end
127
+
128
+ def find_and_prepend(regex, new_line)
129
+ new_lines = []
130
+
131
+ each do |existing_line|
132
+ new_lines << new_line if regex.match(existing_line)
133
+ new_lines << existing_line
134
+ end
135
+
136
+ write_lines_to_file(new_lines, 'write')
137
+ end
138
+
139
+ def find_and_remove(regex)
140
+ new_lines = []
141
+
142
+ each do |existing_line|
143
+ new_lines << existing_line unless regex.match(existing_line)
144
+ end
145
+
146
+ write_lines_to_file(new_lines, 'write')
147
+ end
148
+
149
+ def write(&block)
150
+ return unless writeable?
151
+
152
+ new_lines = []
153
+ block.call(new_lines)
154
+
155
+ backup_file if should_backup?
156
+ write_lines_to_file(new_lines, 'write')
157
+ end
158
+
159
+ def append(&block)
160
+ return unless writeable?
161
+
162
+ new_lines = []
163
+ block.call(new_lines)
164
+
165
+ backup_file if should_backup?
166
+ write_lines_to_file(new_lines, 'append')
167
+ end
168
+
169
+ def prepend(&block)
170
+ return unless writeable?
171
+
172
+ new_lines = []
173
+ block.call(new_lines)
174
+
175
+ backup_file if should_backup?
176
+
177
+ ## If large file, use tmp file to prepend to
178
+ if chunk_read_file?
179
+ tmp_file = "/tmp/kanrisuru-tempfile-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S-%L')}"
180
+
181
+ @host.echo(new_lines.join('\\n'), new_file: tmp_file, backslash: true, mode: 'write')
182
+ @host.cat(@path, new_file: tmp_file, mode: 'append')
183
+ @host.cp(tmp_file, @path)
184
+
185
+ @host.rm(tmp_file)
186
+ reload!
187
+ else
188
+ all_lines = new_lines + @file_lines
189
+ write_lines_to_file(all_lines, 'write')
190
+ end
191
+ end
192
+
193
+ def rename(new_path)
194
+ # @host.mv()
195
+ end
196
+
197
+ def touch
198
+ result = @host.touch(@path)
199
+ update_file_attributes(result[0]) if result.success?
200
+ end
201
+
202
+ def delete
203
+ result = @host.unlink(@path)
204
+
205
+ init_file_attirbutes if result.success?
206
+
207
+ result.success?
208
+ end
209
+
210
+ def each(&block)
211
+ if chunk_read_file?
212
+ each_chunk(&block)
213
+ else
214
+ @file_lines.each { |line| block.call(line) }
215
+ end
216
+ end
217
+
218
+ def reload!
219
+ @writeable = nil
220
+ @file_lines = []
221
+
222
+ wc_file
223
+ stat_file
224
+ read
225
+ end
226
+
227
+ private
228
+
229
+ def should_backup?
230
+ Kanrisuru::Util.present?(@backup)
231
+ end
232
+
233
+ def backup_file
234
+ result = @host.cp(@path, @backup)
235
+ raise "Backup of #{@path} failed" if result.failure?
236
+ end
237
+
238
+ def each_chunk(&block)
239
+ chunk_size = @lines >> 1
240
+ index = 1
241
+
242
+ loop do
243
+ result = @host.read_file_chunk(@path, index, index + chunk_size - 1)
244
+ break if result.failure?
245
+
246
+ lines = result.data
247
+ lines.each { |line| block.call(line) }
248
+
249
+ break if index >= @lines
250
+
251
+ index += chunk_size
252
+ end
253
+ end
254
+
255
+ def read
256
+ return if chunk_read_file?
257
+
258
+ result = @host.cat(@path)
259
+ raise 'Error reading remote file' if result.failure?
260
+
261
+ @file_lines = result.data
262
+ end
263
+
264
+ def write_lines_to_file(lines, mode)
265
+ if lines.length < WRITE_LINE_COUNT
266
+ write_to_file(lines, mode)
267
+ else
268
+ chunk_write_to_file(lines, mode)
269
+ end
270
+ end
271
+
272
+ def write_to_file(lines, mode)
273
+ result = @host.echo(lines.join('\\n'), new_file: @path, backslash: true, mode: mode)
274
+
275
+ reload! if result.success?
276
+ end
277
+
278
+ def chunk_write_to_file(lines, mode)
279
+ ## Clear initial file to write new data
280
+ if mode == 'write'
281
+ @host.unlink(@path)
282
+ @host.touch(@path)
283
+ end
284
+
285
+ lines.each_slice(WRITE_LINE_COUNT) do |lines_slice|
286
+ result = @host.echo(lines_slice.join('\\n'), new_file: @path, backslash: true, mode: 'append')
287
+ raise 'Error appending lines to file' if result.failure?
288
+ end
289
+
290
+ reload!
291
+ end
292
+
293
+ def chunk_read_file?
294
+ @size >= READ_FILE_SIZE
295
+ end
296
+
297
+ def chunk_read_line_by_index(index)
298
+ result = @host.read_file_chunk(@path, index + 1, index + 1)
299
+ result.success? ? result[0] : nil
300
+ end
301
+
302
+ def wc_file
303
+ result = @host.wc(@path)
304
+
305
+ return if result.failure?
306
+
307
+ @lines = result.lines
308
+ @words = result.words
309
+ @chars = result.characters
310
+ end
311
+
312
+ def stat_file
313
+ result = @host.stat(@path)
314
+
315
+ update_file_attributes(result) if result.success?
316
+ end
317
+
318
+ def update_file_attributes(result)
319
+ @mode = result.mode
320
+ @blocks = result.blocks
321
+ @size = result.fsize
322
+ @type = result.file_type
323
+ @gid = result.gid
324
+ @group = result.group
325
+ @uid = result.uid
326
+ @user = result.user
327
+ @inode = result.inode
328
+ @last_access = result.last_access
329
+ @last_modified = result.last_modified
330
+ @last_changed = result.last_changed
331
+ end
332
+
333
+ def init_file_attirbutes
334
+ @writeable = nil
335
+ @mode = nil
336
+ @blocks = nil
337
+ @size = nil
338
+ @type = nil
339
+ @gid = nil
340
+ @group = nil
341
+ @uid = nil
342
+ @user = nil
343
+ @inode = nil
344
+ @last_access = nil
345
+ @last_modified = nil
346
+ @last_changed = nil
347
+
348
+ @lines = nil
349
+ @words = nil
350
+ @chars = nil
351
+ end
352
+ end
353
+ end
354
+ end