kanrisuru 0.1.0 → 0.2.1

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.
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