haveapi-fs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/CHANGELOG +2 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +338 -0
  7. data/Rakefile +1 -0
  8. data/assets/css/bootstrap.min.css +6 -0
  9. data/assets/css/local.css +11 -0
  10. data/bin/haveapi-fs +5 -0
  11. data/haveapi-fs.gemspec +30 -0
  12. data/lib/core_ext/string.rb +9 -0
  13. data/lib/haveapi/fs/auth/base.rb +56 -0
  14. data/lib/haveapi/fs/auth/basic.rb +29 -0
  15. data/lib/haveapi/fs/auth/noauth.rb +9 -0
  16. data/lib/haveapi/fs/auth/token.rb +39 -0
  17. data/lib/haveapi/fs/cache.rb +71 -0
  18. data/lib/haveapi/fs/cleaner.rb +56 -0
  19. data/lib/haveapi/fs/component.rb +237 -0
  20. data/lib/haveapi/fs/components/action_dir.rb +106 -0
  21. data/lib/haveapi/fs/components/action_errors.rb +49 -0
  22. data/lib/haveapi/fs/components/action_exec.rb +12 -0
  23. data/lib/haveapi/fs/components/action_exec_edit.rb +90 -0
  24. data/lib/haveapi/fs/components/action_input.rb +40 -0
  25. data/lib/haveapi/fs/components/action_message.rb +19 -0
  26. data/lib/haveapi/fs/components/action_output.rb +79 -0
  27. data/lib/haveapi/fs/components/action_status.rb +32 -0
  28. data/lib/haveapi/fs/components/cache_stats.rb +19 -0
  29. data/lib/haveapi/fs/components/component_list.rb +24 -0
  30. data/lib/haveapi/fs/components/create_action_dir.rb +15 -0
  31. data/lib/haveapi/fs/components/delete_action_dir.rb +22 -0
  32. data/lib/haveapi/fs/components/directory.rb +45 -0
  33. data/lib/haveapi/fs/components/directory_reset.rb +8 -0
  34. data/lib/haveapi/fs/components/executable.rb +75 -0
  35. data/lib/haveapi/fs/components/file.rb +43 -0
  36. data/lib/haveapi/fs/components/groff_help_file.rb +9 -0
  37. data/lib/haveapi/fs/components/help_file.rb +28 -0
  38. data/lib/haveapi/fs/components/html_help_file.rb +24 -0
  39. data/lib/haveapi/fs/components/index_filter.rb +63 -0
  40. data/lib/haveapi/fs/components/info_files.rb +19 -0
  41. data/lib/haveapi/fs/components/instance_create.rb +20 -0
  42. data/lib/haveapi/fs/components/instance_edit.rb +49 -0
  43. data/lib/haveapi/fs/components/list_item.rb +28 -0
  44. data/lib/haveapi/fs/components/md_help_file.rb +24 -0
  45. data/lib/haveapi/fs/components/meta_dir.rb +42 -0
  46. data/lib/haveapi/fs/components/meta_file.rb +21 -0
  47. data/lib/haveapi/fs/components/parameter.rb +132 -0
  48. data/lib/haveapi/fs/components/pry.rb +9 -0
  49. data/lib/haveapi/fs/components/remote_control_file.rb +92 -0
  50. data/lib/haveapi/fs/components/resource_action_dir.rb +72 -0
  51. data/lib/haveapi/fs/components/resource_dir.rb +161 -0
  52. data/lib/haveapi/fs/components/resource_id.rb +15 -0
  53. data/lib/haveapi/fs/components/resource_instance_dir.rb +146 -0
  54. data/lib/haveapi/fs/components/rfuse_check.rb +3 -0
  55. data/lib/haveapi/fs/components/root.rb +75 -0
  56. data/lib/haveapi/fs/components/save_instance.rb +11 -0
  57. data/lib/haveapi/fs/components/unsaved_list.rb +24 -0
  58. data/lib/haveapi/fs/components/update_action_dir.rb +31 -0
  59. data/lib/haveapi/fs/context.rb +43 -0
  60. data/lib/haveapi/fs/exceptions.rb +0 -0
  61. data/lib/haveapi/fs/factory.rb +59 -0
  62. data/lib/haveapi/fs/fs.rb +198 -0
  63. data/lib/haveapi/fs/help.rb +91 -0
  64. data/lib/haveapi/fs/main.rb +134 -0
  65. data/lib/haveapi/fs/remote_control.rb +29 -0
  66. data/lib/haveapi/fs/version.rb +5 -0
  67. data/lib/haveapi/fs/worker.rb +77 -0
  68. data/lib/haveapi/fs.rb +65 -0
  69. data/templates/help/html/action_dir.erb +33 -0
  70. data/templates/help/html/action_errors.erb +4 -0
  71. data/templates/help/html/action_input.erb +40 -0
  72. data/templates/help/html/action_output.erb +21 -0
  73. data/templates/help/html/index_filter.erb +16 -0
  74. data/templates/help/html/layout.erb +45 -0
  75. data/templates/help/html/resource_action_dir.erb +18 -0
  76. data/templates/help/html/resource_dir.erb +18 -0
  77. data/templates/help/html/resource_instance_dir.erb +64 -0
  78. data/templates/help/html/root.erb +42 -0
  79. data/templates/help/md/action_dir.erb +29 -0
  80. data/templates/help/md/action_errors.erb +2 -0
  81. data/templates/help/md/action_input.erb +23 -0
  82. data/templates/help/md/action_output.erb +11 -0
  83. data/templates/help/md/index_filter.erb +11 -0
  84. data/templates/help/md/layout.erb +14 -0
  85. data/templates/help/md/resource_action_dir.erb +10 -0
  86. data/templates/help/md/resource_dir.erb +15 -0
  87. data/templates/help/md/resource_instance_dir.erb +42 -0
  88. data/templates/help/md/root.erb +34 -0
  89. 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,3 @@
1
+ module HaveAPI::Fs::Components
2
+ class RFuseCheck < File ; end
3
+ 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,11 @@
1
+ module HaveAPI::Fs::Components
2
+ class SaveInstance < Executable
3
+ def initialize(resource_dir, *args)
4
+ super(*args)
5
+ @resource_dir = resource_dir
6
+ end
7
+ def exec
8
+ @resource_dir.save
9
+ end
10
+ end
11
+ 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