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