haveapi-fs 0.1.0
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/.yardopts +1 -0
- data/CHANGELOG +2 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +338 -0
- data/Rakefile +1 -0
- data/assets/css/bootstrap.min.css +6 -0
- data/assets/css/local.css +11 -0
- data/bin/haveapi-fs +5 -0
- data/haveapi-fs.gemspec +30 -0
- data/lib/core_ext/string.rb +9 -0
- data/lib/haveapi/fs/auth/base.rb +56 -0
- data/lib/haveapi/fs/auth/basic.rb +29 -0
- data/lib/haveapi/fs/auth/noauth.rb +9 -0
- data/lib/haveapi/fs/auth/token.rb +39 -0
- data/lib/haveapi/fs/cache.rb +71 -0
- data/lib/haveapi/fs/cleaner.rb +56 -0
- data/lib/haveapi/fs/component.rb +237 -0
- data/lib/haveapi/fs/components/action_dir.rb +106 -0
- data/lib/haveapi/fs/components/action_errors.rb +49 -0
- data/lib/haveapi/fs/components/action_exec.rb +12 -0
- data/lib/haveapi/fs/components/action_exec_edit.rb +90 -0
- data/lib/haveapi/fs/components/action_input.rb +40 -0
- data/lib/haveapi/fs/components/action_message.rb +19 -0
- data/lib/haveapi/fs/components/action_output.rb +79 -0
- data/lib/haveapi/fs/components/action_status.rb +32 -0
- data/lib/haveapi/fs/components/cache_stats.rb +19 -0
- data/lib/haveapi/fs/components/component_list.rb +24 -0
- data/lib/haveapi/fs/components/create_action_dir.rb +15 -0
- data/lib/haveapi/fs/components/delete_action_dir.rb +22 -0
- data/lib/haveapi/fs/components/directory.rb +45 -0
- data/lib/haveapi/fs/components/directory_reset.rb +8 -0
- data/lib/haveapi/fs/components/executable.rb +75 -0
- data/lib/haveapi/fs/components/file.rb +43 -0
- data/lib/haveapi/fs/components/groff_help_file.rb +9 -0
- data/lib/haveapi/fs/components/help_file.rb +28 -0
- data/lib/haveapi/fs/components/html_help_file.rb +24 -0
- data/lib/haveapi/fs/components/index_filter.rb +63 -0
- data/lib/haveapi/fs/components/info_files.rb +19 -0
- data/lib/haveapi/fs/components/instance_create.rb +20 -0
- data/lib/haveapi/fs/components/instance_edit.rb +49 -0
- data/lib/haveapi/fs/components/list_item.rb +28 -0
- data/lib/haveapi/fs/components/md_help_file.rb +24 -0
- data/lib/haveapi/fs/components/meta_dir.rb +42 -0
- data/lib/haveapi/fs/components/meta_file.rb +21 -0
- data/lib/haveapi/fs/components/parameter.rb +132 -0
- data/lib/haveapi/fs/components/pry.rb +9 -0
- data/lib/haveapi/fs/components/remote_control_file.rb +92 -0
- data/lib/haveapi/fs/components/resource_action_dir.rb +72 -0
- data/lib/haveapi/fs/components/resource_dir.rb +161 -0
- data/lib/haveapi/fs/components/resource_id.rb +15 -0
- data/lib/haveapi/fs/components/resource_instance_dir.rb +146 -0
- data/lib/haveapi/fs/components/rfuse_check.rb +3 -0
- data/lib/haveapi/fs/components/root.rb +75 -0
- data/lib/haveapi/fs/components/save_instance.rb +11 -0
- data/lib/haveapi/fs/components/unsaved_list.rb +24 -0
- data/lib/haveapi/fs/components/update_action_dir.rb +31 -0
- data/lib/haveapi/fs/context.rb +43 -0
- data/lib/haveapi/fs/exceptions.rb +0 -0
- data/lib/haveapi/fs/factory.rb +59 -0
- data/lib/haveapi/fs/fs.rb +198 -0
- data/lib/haveapi/fs/help.rb +91 -0
- data/lib/haveapi/fs/main.rb +134 -0
- data/lib/haveapi/fs/remote_control.rb +29 -0
- data/lib/haveapi/fs/version.rb +5 -0
- data/lib/haveapi/fs/worker.rb +77 -0
- data/lib/haveapi/fs.rb +65 -0
- data/templates/help/html/action_dir.erb +33 -0
- data/templates/help/html/action_errors.erb +4 -0
- data/templates/help/html/action_input.erb +40 -0
- data/templates/help/html/action_output.erb +21 -0
- data/templates/help/html/index_filter.erb +16 -0
- data/templates/help/html/layout.erb +45 -0
- data/templates/help/html/resource_action_dir.erb +18 -0
- data/templates/help/html/resource_dir.erb +18 -0
- data/templates/help/html/resource_instance_dir.erb +64 -0
- data/templates/help/html/root.erb +42 -0
- data/templates/help/md/action_dir.erb +29 -0
- data/templates/help/md/action_errors.erb +2 -0
- data/templates/help/md/action_input.erb +23 -0
- data/templates/help/md/action_output.erb +11 -0
- data/templates/help/md/index_filter.erb +11 -0
- data/templates/help/md/layout.erb +14 -0
- data/templates/help/md/resource_action_dir.erb +10 -0
- data/templates/help/md/resource_dir.erb +15 -0
- data/templates/help/md/resource_instance_dir.erb +42 -0
- data/templates/help/md/root.erb +34 -0
- metadata +231 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module HaveAPI::Fs::Components
|
|
4
|
+
# This file serves as an IPC between the file system and outer processes,
|
|
5
|
+
# mainly executables from the file system itself.
|
|
6
|
+
#
|
|
7
|
+
# It does not behave like a regular file, it's more like a local socket.
|
|
8
|
+
# The process opens the file, writes a message with a command and then reads
|
|
9
|
+
# a message with a response.
|
|
10
|
+
#
|
|
11
|
+
# Messages are formatted in YAML and separated by {MSG_DELIMITER}.
|
|
12
|
+
class RemoteControlFile < File
|
|
13
|
+
MSG_DELIMITER = "\nMSG_OVER\n"
|
|
14
|
+
|
|
15
|
+
class FileHandle
|
|
16
|
+
attr_accessor :read_buf
|
|
17
|
+
attr_accessor :write_buf
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@read_buf = ''
|
|
21
|
+
@write_buf = ''
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def read(offset, size)
|
|
25
|
+
@read_buf[offset, size]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def write(offset, size, buf)
|
|
29
|
+
@write_buf[offset, size] = buf
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def complete?
|
|
33
|
+
@write_buf.end_with?(MSG_DELIMITER)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def parse
|
|
37
|
+
cmd = YAML.load(@write_buf[0..(-1 - MSG_DELIMITER.size)])
|
|
38
|
+
@write_buf.clear
|
|
39
|
+
cmd
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def writable?
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def size
|
|
48
|
+
# The size limits the maximum amount of data that can be read from this file
|
|
49
|
+
4096
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def raw_open(path, mode, rfusefs = nil)
|
|
53
|
+
FileHandle.new
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def raw_read(path, offset, size, handle = nil)
|
|
57
|
+
handle.read(offset, size)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def raw_write(path, offset, size, buf, handle = nil)
|
|
61
|
+
handle.write(offset, size, buf)
|
|
62
|
+
|
|
63
|
+
if handle.complete?
|
|
64
|
+
cmd = handle.parse
|
|
65
|
+
|
|
66
|
+
case cmd[:action]
|
|
67
|
+
when :execute
|
|
68
|
+
ret = HaveAPI::Fs::RemoteControl.execute(context, cmd[:path])
|
|
69
|
+
|
|
70
|
+
else
|
|
71
|
+
raise Errno::EIO, "unsupported action '#{cmd[:action]}'"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
handle.read_buf = YAML.dump(ret)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
size
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def raw_sync(path, datasync, handle = nil)
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def raw_truncate(path, offset, handle = nil)
|
|
85
|
+
true
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def raw_close(path, handle = nil)
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module HaveAPI::Fs::Components
|
|
2
|
+
class ResourceActionDir < Directory
|
|
3
|
+
component :resource_action_dir
|
|
4
|
+
attr_reader :resource
|
|
5
|
+
|
|
6
|
+
def initialize(r)
|
|
7
|
+
@resource = r
|
|
8
|
+
@instance = r.is_a?(HaveAPI::Client::ResourceInstance)
|
|
9
|
+
|
|
10
|
+
super()
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def contents
|
|
14
|
+
super + relevant_actions.map(&:to_s)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def relevant_actions
|
|
18
|
+
return @actions if @actions
|
|
19
|
+
@actions = []
|
|
20
|
+
|
|
21
|
+
@resource.actions.each do |name, a|
|
|
22
|
+
pos = a.url.index(":#{@resource._name}_id")
|
|
23
|
+
|
|
24
|
+
if @instance
|
|
25
|
+
cond = pos
|
|
26
|
+
|
|
27
|
+
else
|
|
28
|
+
cond = pos.nil?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@actions << name if cond
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@actions
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def instance?
|
|
38
|
+
@instance
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def title
|
|
42
|
+
'Actions'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
protected
|
|
46
|
+
def new_child(name)
|
|
47
|
+
if child = super
|
|
48
|
+
child
|
|
49
|
+
|
|
50
|
+
elsif @resource.actions.has_key?(name)
|
|
51
|
+
klass = case name
|
|
52
|
+
when :create
|
|
53
|
+
CreateActionDir
|
|
54
|
+
|
|
55
|
+
when :update
|
|
56
|
+
instance? ? UpdateActionDir : ActionDir
|
|
57
|
+
|
|
58
|
+
when :delete
|
|
59
|
+
DeleteActionDir
|
|
60
|
+
|
|
61
|
+
else
|
|
62
|
+
ActionDir
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
[klass, @resource, @resource.actions[name]]
|
|
66
|
+
|
|
67
|
+
else
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
module HaveAPI::Fs::Components
|
|
2
|
+
class ResourceDir < Directory
|
|
3
|
+
component :resource_dir
|
|
4
|
+
attr_reader :resource
|
|
5
|
+
|
|
6
|
+
def initialize(resource)
|
|
7
|
+
super()
|
|
8
|
+
@resource = resource
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def setup
|
|
12
|
+
super
|
|
13
|
+
@index = use(:actions, :index)
|
|
14
|
+
@data = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def contents
|
|
18
|
+
load_contents if @index && (!@data || @refresh)
|
|
19
|
+
|
|
20
|
+
ret = super + %w(actions) + subresources.map(&:to_s)
|
|
21
|
+
ret.concat(@data.map { |v| v.id.to_s }) if @data
|
|
22
|
+
ret << 'create.yml' if find(:actions).find(:create)
|
|
23
|
+
|
|
24
|
+
if @index
|
|
25
|
+
ret.concat(@index.action.input_params.keys.map { |v| "by-#{v}" })
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
ret
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def refresh
|
|
32
|
+
@refresh = true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def delete(id)
|
|
36
|
+
return unless @data
|
|
37
|
+
i = @data.index { |v| v.id == id }
|
|
38
|
+
@data.delete_at(i) if i
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def title
|
|
42
|
+
"Resource #{@resource._name.to_s.capitalize}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
protected
|
|
46
|
+
def new_child(name)
|
|
47
|
+
if child = super
|
|
48
|
+
child
|
|
49
|
+
|
|
50
|
+
elsif name == :actions
|
|
51
|
+
[ResourceActionDir, @resource]
|
|
52
|
+
|
|
53
|
+
elsif subresources.include?(name)
|
|
54
|
+
[ResourceDir, @resource.send(name)]
|
|
55
|
+
|
|
56
|
+
elsif /^\d+$/ =~ name
|
|
57
|
+
id = name.to_s.to_i
|
|
58
|
+
|
|
59
|
+
if @data
|
|
60
|
+
r = @data.detect { |v| v.id == id }
|
|
61
|
+
[ResourceInstanceDir, r] if r
|
|
62
|
+
|
|
63
|
+
else
|
|
64
|
+
# The directory contents have not been loaded yet. We don't necessarily
|
|
65
|
+
# need to load it all, a single query should be sufficient.
|
|
66
|
+
begin
|
|
67
|
+
obj = @resource.show(id, meta: meta_params)
|
|
68
|
+
[ResourceInstanceDir, obj]
|
|
69
|
+
|
|
70
|
+
rescue HaveAPI::Client::ActionFailed
|
|
71
|
+
# Not found
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
elsif name == :'create.yml' && create_dir = use(:actions, :create)
|
|
76
|
+
[InstanceCreate, create_dir]
|
|
77
|
+
|
|
78
|
+
elsif @index && name.to_s.start_with?('by-')
|
|
79
|
+
by_param = name.to_s[3..-1].to_sym
|
|
80
|
+
return nil unless @index.action.input_params.has_key?(by_param)
|
|
81
|
+
|
|
82
|
+
[IndexFilter, self, by_param]
|
|
83
|
+
|
|
84
|
+
else
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def subresources
|
|
90
|
+
return @subresources if @subresources
|
|
91
|
+
@subresources = []
|
|
92
|
+
|
|
93
|
+
@resource.resources.each do |r_name, r|
|
|
94
|
+
r.actions.each do |a_name, a|
|
|
95
|
+
if a.url.index(":#{@resource._name}_id").nil?
|
|
96
|
+
@subresources << r_name
|
|
97
|
+
break
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
@subresources
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def meta_params
|
|
106
|
+
{
|
|
107
|
+
includes: @resource.actions[:show].params.select do |n, p|
|
|
108
|
+
p[:type] == 'Resource'
|
|
109
|
+
end.map do |n, p|
|
|
110
|
+
n
|
|
111
|
+
end.join(',')
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def load_contents
|
|
116
|
+
if context.opts[:index_limit] && file = @index.find(:input).find(:limit)
|
|
117
|
+
limit = context.opts[:index_limit].to_i
|
|
118
|
+
v = file.value
|
|
119
|
+
param = @index.action.input_params[:limit]
|
|
120
|
+
|
|
121
|
+
if (!v && !param[:default]) \
|
|
122
|
+
|| (v && v > limit) \
|
|
123
|
+
|| (param[:default] && param[:default] > limit)
|
|
124
|
+
file.write_safe(limit)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
@index.exec(meta: meta_params)
|
|
129
|
+
new_data = @index.output.data
|
|
130
|
+
|
|
131
|
+
if @data
|
|
132
|
+
current_map = id_map(@data)
|
|
133
|
+
res = []
|
|
134
|
+
|
|
135
|
+
new_data.each do |v|
|
|
136
|
+
if current_map.has_key?(v.id)
|
|
137
|
+
# TODO: if old object is not modified, use the new object instead
|
|
138
|
+
res << current_map[v.id]
|
|
139
|
+
|
|
140
|
+
else
|
|
141
|
+
res << v
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
@data = res
|
|
146
|
+
|
|
147
|
+
else
|
|
148
|
+
@data = new_data
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
changed
|
|
152
|
+
@refresh = false
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def id_map(list)
|
|
156
|
+
ret = {}
|
|
157
|
+
list.each { |v| ret[v.id] = v }
|
|
158
|
+
ret
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module HaveAPI::Fs::Components
|
|
2
|
+
class ResourceId < Parameter
|
|
3
|
+
def initialize(resource_dir, *args)
|
|
4
|
+
super(*args)
|
|
5
|
+
|
|
6
|
+
@resource_dir = resource_dir
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def write(str)
|
|
10
|
+
super(str)
|
|
11
|
+
|
|
12
|
+
@resource_dir.replace_association(@name, value)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
module HaveAPI::Fs::Components
|
|
2
|
+
class ResourceInstanceDir < ResourceDir
|
|
3
|
+
component :resource_instance_dir
|
|
4
|
+
|
|
5
|
+
def setup
|
|
6
|
+
super
|
|
7
|
+
|
|
8
|
+
@show = use(:actions, :show)
|
|
9
|
+
@update = use(:actions, :update)
|
|
10
|
+
children[:save] = [SaveInstance, self, bound: true] if @update
|
|
11
|
+
|
|
12
|
+
# Disable object listing from ResourceDir.contents
|
|
13
|
+
@index = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def contents
|
|
17
|
+
ret = super - %w(create.yml)
|
|
18
|
+
ret.concat(attributes)
|
|
19
|
+
ret.concat(%w(save edit.yml)) if @update
|
|
20
|
+
ret
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def save
|
|
24
|
+
ret = @update.exec
|
|
25
|
+
self.mtime = Time.now
|
|
26
|
+
ret
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def replace_association(name, id)
|
|
30
|
+
return unless children.has_key?(name)
|
|
31
|
+
|
|
32
|
+
children[name].invalidate
|
|
33
|
+
context.cache.drop_below(path)
|
|
34
|
+
|
|
35
|
+
@resource.send("#{name}_id=", id)
|
|
36
|
+
children[name] = [ResourceInstanceDir, @resource.send(name)]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def update_association(name)
|
|
40
|
+
children[name].invalidate
|
|
41
|
+
context.cache.drop_below(path)
|
|
42
|
+
|
|
43
|
+
children[name] = [ResourceInstanceDir, @resource.send(name)]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def subresources
|
|
47
|
+
return @subresources if @subresources
|
|
48
|
+
@subresources = []
|
|
49
|
+
|
|
50
|
+
@resource.resources.each do |r_name, r|
|
|
51
|
+
r.actions.each do |a_name, a|
|
|
52
|
+
if a.url.index(":#{@resource._name}_id")
|
|
53
|
+
@subresources << r_name
|
|
54
|
+
break
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
@subresources
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def attributes
|
|
63
|
+
ret = []
|
|
64
|
+
params = @resource.actions[:show].params
|
|
65
|
+
|
|
66
|
+
@resource.attributes.each do |k, v|
|
|
67
|
+
next if k == :_meta
|
|
68
|
+
|
|
69
|
+
if params[k][:type] == 'Resource'
|
|
70
|
+
ret << "#{k}_id"
|
|
71
|
+
ret << k.to_s if v
|
|
72
|
+
|
|
73
|
+
else
|
|
74
|
+
ret << k.to_s
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
ret
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def title
|
|
82
|
+
"#{@resource._name.to_s.capitalize} ##{@resource.id}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
protected
|
|
86
|
+
def new_child(name)
|
|
87
|
+
if child = Directory.instance_method(:new_child).bind(self).call(name)
|
|
88
|
+
child
|
|
89
|
+
|
|
90
|
+
elsif name == :actions
|
|
91
|
+
[ResourceActionDir, @resource]
|
|
92
|
+
|
|
93
|
+
elsif subresources.include?(name)
|
|
94
|
+
[ResourceDir, @resource.send(name)]
|
|
95
|
+
|
|
96
|
+
elsif @resource.attributes.has_key?(name)
|
|
97
|
+
if @show.action.params[name][:type] == 'Resource'
|
|
98
|
+
instance = @resource.send(name)
|
|
99
|
+
|
|
100
|
+
if instance
|
|
101
|
+
[ResourceInstanceDir, instance]
|
|
102
|
+
|
|
103
|
+
else
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
else
|
|
108
|
+
editable = @update.nil? ? false : @update.action.input_params.has_key?(name)
|
|
109
|
+
|
|
110
|
+
[
|
|
111
|
+
Parameter,
|
|
112
|
+
@resource.actions[:show],
|
|
113
|
+
name,
|
|
114
|
+
:output,
|
|
115
|
+
@resource,
|
|
116
|
+
editable: editable,
|
|
117
|
+
mirror: editable && @update.find(:input).find(name),
|
|
118
|
+
]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
elsif name.to_s.end_with?('_id')
|
|
122
|
+
real_name = name[0..-4].to_sym
|
|
123
|
+
return nil unless @resource.attributes.has_key?(real_name)
|
|
124
|
+
|
|
125
|
+
editable = @update.nil? ? false : @update.action.input_params.has_key?(real_name)
|
|
126
|
+
|
|
127
|
+
[
|
|
128
|
+
ResourceId,
|
|
129
|
+
self,
|
|
130
|
+
@resource.actions[:show],
|
|
131
|
+
real_name,
|
|
132
|
+
:output,
|
|
133
|
+
@resource,
|
|
134
|
+
editable: editable,
|
|
135
|
+
mirror: editable && @update.find(:input).find(real_name),
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
elsif name == :'edit.yml' && @update
|
|
139
|
+
[InstanceEdit, @update]
|
|
140
|
+
|
|
141
|
+
else
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module HaveAPI::Fs::Components
|
|
2
|
+
# The root of the filesystem. There is only one instance of this object.
|
|
3
|
+
#
|
|
4
|
+
# This directory contains some special hidden files:
|
|
5
|
+
#
|
|
6
|
+
# - `.remote_control` is used for IPC between the file system and executables
|
|
7
|
+
# - `.cache` contains some statistics about the cache
|
|
8
|
+
# - `.assets/` contains static files for HTML help files
|
|
9
|
+
class Root < Directory
|
|
10
|
+
component :root
|
|
11
|
+
|
|
12
|
+
def initialize()
|
|
13
|
+
super()
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def setup
|
|
17
|
+
super
|
|
18
|
+
@api = context.fs.api
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def contents
|
|
22
|
+
super + %w(.client_version .fs_version .protocol_version) + \
|
|
23
|
+
@api.resources.keys.map(&:to_s)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def resources
|
|
27
|
+
@api.resources
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def title
|
|
31
|
+
context.url
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
protected
|
|
35
|
+
def new_child(name)
|
|
36
|
+
if child = super
|
|
37
|
+
child
|
|
38
|
+
|
|
39
|
+
elsif @api.resources.has_key?(name)
|
|
40
|
+
[ResourceDir, @api.resources[name]]
|
|
41
|
+
|
|
42
|
+
else
|
|
43
|
+
case name
|
|
44
|
+
when :'.remote_control'
|
|
45
|
+
RemoteControlFile
|
|
46
|
+
|
|
47
|
+
when :'.assets'
|
|
48
|
+
[
|
|
49
|
+
MetaDir,
|
|
50
|
+
::File.join(
|
|
51
|
+
::File.realpath(::File.dirname(__FILE__)),
|
|
52
|
+
'..', '..', '..', '..',
|
|
53
|
+
'assets'
|
|
54
|
+
),
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
when :'.client_version'
|
|
58
|
+
ClientVersion
|
|
59
|
+
|
|
60
|
+
when :'.fs_version'
|
|
61
|
+
FsVersion
|
|
62
|
+
|
|
63
|
+
when :'.protocol_version'
|
|
64
|
+
ProtocolVersion
|
|
65
|
+
|
|
66
|
+
when :'.cache'
|
|
67
|
+
CacheStats
|
|
68
|
+
|
|
69
|
+
else
|
|
70
|
+
nil
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module HaveAPI::Fs::Components
|
|
2
|
+
class UnsavedList < File
|
|
3
|
+
def read
|
|
4
|
+
str = list_unsaved.join("\n")
|
|
5
|
+
str += "\n" unless str.empty?
|
|
6
|
+
str
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
protected
|
|
10
|
+
def list_unsaved(component = nil)
|
|
11
|
+
component ||= parent
|
|
12
|
+
ret = []
|
|
13
|
+
|
|
14
|
+
component.send(:children).each do |_, c|
|
|
15
|
+
next unless c.unsaved?
|
|
16
|
+
|
|
17
|
+
ret << c.path
|
|
18
|
+
ret.concat(list_unsaved(c))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
ret
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module HaveAPI::Fs::Components
|
|
2
|
+
class UpdateActionDir < ActionDir
|
|
3
|
+
help_file :action_dir
|
|
4
|
+
|
|
5
|
+
def exec
|
|
6
|
+
ret = super
|
|
7
|
+
|
|
8
|
+
return ret if !ret.is_a?(HaveAPI::Client::Response) || !ret.ok?
|
|
9
|
+
|
|
10
|
+
data = children[:output].data
|
|
11
|
+
return ret unless data.is_a?(HaveAPI::Client::ResourceInstance)
|
|
12
|
+
|
|
13
|
+
params = @resource.actions[:show].params
|
|
14
|
+
attrs = @resource.attributes
|
|
15
|
+
|
|
16
|
+
data.attributes.each do |k, v|
|
|
17
|
+
next if %i(id _meta).include?(k) || !attrs.has_key?(k)
|
|
18
|
+
|
|
19
|
+
if params[k][:type] == 'Resource'
|
|
20
|
+
@resource.send("#{k}=", data.send(k))
|
|
21
|
+
context[:resource_instance_dir].update_association(k)
|
|
22
|
+
|
|
23
|
+
else
|
|
24
|
+
@resource.send("#{k}=", v)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
ret
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module HaveAPI::Fs
|
|
2
|
+
# Every component has its own Context instance and passes it along to its
|
|
3
|
+
# children. Context is used to pass information from the root of the tree
|
|
4
|
+
# to all its branches and leaves. Using the context, every component knows
|
|
5
|
+
# all its parents and can relate to them. It also contains a reference
|
|
6
|
+
# to the global {Cache} instance.
|
|
7
|
+
class Context
|
|
8
|
+
attr_accessor :object_path, :file_path, :opts, :url, :mountpoint, :cache
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@object_path = []
|
|
12
|
+
@file_path = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def clone
|
|
16
|
+
c = super
|
|
17
|
+
c.object_path = c.object_path.clone
|
|
18
|
+
c.file_path = c.file_path.clone
|
|
19
|
+
c
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def set(name, value)
|
|
23
|
+
@object_path << [name, value]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def []=(name, value)
|
|
27
|
+
set(name, value)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def [](name)
|
|
31
|
+
item = @object_path.reverse.detect { |v| v[0] == name }
|
|
32
|
+
item && item[1]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def last
|
|
36
|
+
@object_path.last[1]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def method_missing(name, *args)
|
|
40
|
+
self[name] || super
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
File without changes
|