haveapi-fs 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|