oneis 1.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
data/lib/manifest.rb ADDED
@@ -0,0 +1,41 @@
1
+
2
+ module PluginTool
3
+
4
+ ALLOWED_PLUGIN_DIRS = ['js', 'static', 'template', 'test', 'data']
5
+
6
+ def self.generate_manifest(directory)
7
+ manifest = Hash.new
8
+ Dir.glob("#{directory}/**/*").sort.each do |pathname|
9
+ # Ignore directories
10
+ next unless File.file? pathname
11
+ # Check file
12
+ filename = pathname.slice(directory.length + 1, pathname.length)
13
+ raise "Bad filename for #{filename}" unless filename =~ /\A([a-zA-Z0-9_\/\-]+\/)?([a-z0-9_-]+\.[a-z0-9]+)\Z/
14
+ dir = $1
15
+ name = $2
16
+ if dir != nil
17
+ dir = dir.gsub(/\/\Z/,'')
18
+ raise "Bad directory #{dir}" unless dir =~ /\A([a-zA-Z0-9_\-]+)[a-zA-Z0-9_\/\-]*\Z/
19
+ raise "Bad root directory #{$1}" unless ALLOWED_PLUGIN_DIRS.include?($1)
20
+ end
21
+ # Get hash of file
22
+ digest = File.open(pathname) { |f| Digest::SHA1.hexdigest(f.read) }
23
+ # And add to manifest
24
+ manifest[filename] = digest
25
+ end
26
+ manifest
27
+ end
28
+
29
+ def self.determine_manifest_changes(from, to)
30
+ changes = []
31
+ from.each_key do |name|
32
+ changes << [name, :delete] unless to.has_key?(name)
33
+ end
34
+ to.each do |name,hash|
35
+ changes << [name, hash] unless from[name] == hash
36
+ end
37
+ changes
38
+ end
39
+
40
+ end
41
+
data/lib/minimise.rb ADDED
@@ -0,0 +1,68 @@
1
+
2
+ module PluginTool
3
+
4
+ class Minimiser
5
+ Context = Java::OrgMozillaJavascript::Context
6
+
7
+ def initialize
8
+ # Load UglifyJS into a JavaScript interpreter
9
+ raise "Another JS Context is active" unless nil == Context.getCurrentContext()
10
+ @cx = Context.enter();
11
+ @cx.setLanguageVersion(Context::VERSION_1_7)
12
+ @javascript_scope = @cx.initStandardObjects()
13
+ ['js_min.js','uglifyjs/parse-js.js','uglifyjs/process.js','uglifyjs/squeeze-more.js'].each do |filename|
14
+ js = File.open("#{File.dirname(__FILE__)}/#{filename}") { |f| f.read }
15
+ @cx.evaluateString(@javascript_scope, js, "<#{filename}>", 1, nil);
16
+ end
17
+ @js_min = @javascript_scope.get("js_min", @javascript_scope);
18
+ end
19
+
20
+ def process(data, filename)
21
+ if filename =~ /\.js\Z/
22
+ # JavaScript - use UglifyJS loaded into the JavaScript interpreter
23
+ @js_min.call(@cx, @javascript_scope, @javascript_scope, [data])
24
+
25
+ elsif filename =~ /\.html\Z/
26
+ # Simple processing of HTML
27
+ # Remove HTML comments
28
+ html = data.gsub(/\<\!\-\-.+?\-\-\>/m,'')
29
+ # Remove indents
30
+ html.gsub!(/^\s+/,'')
31
+ # Remove any unnecessary line breaks (fairly conservative)
32
+ html.gsub!(/\>[\r\n]+\</m,'><')
33
+ html.gsub!(/([\>}])[\r\n]+([\<{])/m,'\1\2')
34
+ html
35
+
36
+ elsif filename =~ /\.css\Z/
37
+ # Simple processing of CSS
38
+ css = data.gsub(/(^|\s)\/\*.+?\*\/($|\s)/m,'') # remove C style comments
39
+ out = []
40
+ css.split(/[\r\n]+/).each do |line|
41
+ line.chomp!; line.gsub!(/^\s+/,''); line.gsub!(/\s+$/,'')
42
+ line.gsub!(/\s+/,' ') # contract spaces
43
+ line.gsub!(/\s*:\s*/,':') # remove unnecessary spaces
44
+ if line =~ /\S/
45
+ out << line
46
+ end
47
+ end
48
+ css = out.join("\n")
49
+ # Remove unnecessary line endings
50
+ css.gsub!(/[\r\n]*(\{[^\}]+\})/m) do |m|
51
+ $1.gsub(/[\r\n]/m,'')
52
+ end
53
+ css
54
+
55
+ else
56
+ # No processing
57
+ data
58
+ end
59
+ end
60
+
61
+ def finish
62
+ Context.exit()
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+
data/lib/misc.rb ADDED
@@ -0,0 +1,8 @@
1
+
2
+ module PluginTool
3
+
4
+ def self.beep
5
+ $stdout.write("\07")
6
+ end
7
+
8
+ end
data/lib/new_plugin.rb ADDED
@@ -0,0 +1,91 @@
1
+
2
+ module PluginTool
3
+
4
+ def self.make_new_plugin(plugin_name)
5
+ unless plugin_name =~ /\A[a-z0-9_]+\Z/ && plugin_name.length > 8
6
+ end_on_error "Bad plugin name - must use a-z0-9_ only, and more than 8 characters."
7
+ end
8
+ if File.exist?(plugin_name)
9
+ end_on_error "File or directory #{plugin_name} already exists"
10
+ end
11
+ FileUtils.mkdir(plugin_name)
12
+ ['js', 'static', 'template', 'test'].each do |dir|
13
+ FileUtils.mkdir("#{plugin_name}/#{dir}")
14
+ end
15
+ random = java.security.SecureRandom.new()
16
+ rbytes = Java::byte[20].new
17
+ random.nextBytes(rbytes)
18
+ install_secret = String.from_java_bytes(rbytes).unpack('H*').join
19
+ File.open("#{plugin_name}/plugin.json",'w') do |file|
20
+ file.write(<<__E)
21
+ {
22
+ "pluginName": "#{plugin_name}",
23
+ "pluginAuthor": "TODO Your Company",
24
+ "pluginVersion": 1,
25
+ "displayName": "#{plugin_name.split('_').map {|e| e.capitalize} .join(' ')}",
26
+ "displayDescription": "TODO Longer description of plugin",
27
+ "installSecret": "#{install_secret}",
28
+ "apiVersion": 0,
29
+ "load": ["js/#{plugin_name}.js"],
30
+ "respond": ["/do/#{plugin_name}"]
31
+ }
32
+ __E
33
+ end
34
+ File.open("#{plugin_name}/js/#{plugin_name}.js",'w') do |file|
35
+ file.write(<<__E)
36
+
37
+ var #{plugin_name} = O.plugin("#{plugin_name}", {
38
+ });
39
+
40
+ (function() {
41
+
42
+ #{plugin_name}.respond("GET", "/do/#{plugin_name}/example", [
43
+ ], function(E) {
44
+ E.render({
45
+ pageTitle: "Example page"
46
+ });
47
+ });
48
+
49
+ })();
50
+
51
+ __E
52
+ end
53
+ File.open("#{plugin_name}/template/example.html",'w') do |file|
54
+ file.write(<<__E)
55
+ <p>This is an example template.</p>
56
+ __E
57
+ end
58
+ File.open("#{plugin_name}/test/#{plugin_name}_test1.js",'w') do |file|
59
+ file.write(<<__E)
60
+
61
+ T.test(function() {
62
+
63
+ // For documentation, see
64
+ // http://docs.oneis.co.uk/dev/plugin/tests
65
+
66
+ T.assert(true);
67
+
68
+ });
69
+
70
+ __E
71
+ end
72
+ puts <<__E
73
+
74
+ Plugin #{plugin_name} has been created. Run
75
+
76
+ oneis-plugin -p #{plugin_name}
77
+
78
+ to upload it to the server, then visit
79
+
80
+ https://<HOSTNAME>/do/#{plugin_name}/example
81
+
82
+ to see a sample page.
83
+
84
+ Add additional hook handlers in the js/#{plugin_name}.js file.
85
+
86
+ See http://docs.oneis.co.uk/dev/plugin for more information.
87
+
88
+ __E
89
+ end
90
+
91
+ end
@@ -0,0 +1,69 @@
1
+
2
+ module PluginTool
3
+
4
+ @@notification_announce_reconnect = false
5
+
6
+ def self.start_notifications
7
+ @@notification_queue_name = nil
8
+ Thread.new do
9
+ while true
10
+ begin
11
+ self.do_notifications
12
+ rescue => e
13
+ puts "NOTICE: Lost notification connection, will attempt to reconnect soon."
14
+ @@notification_announce_reconnect = true
15
+ sleep(5) # throttle errors
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.do_notifications
22
+ http = make_http_connection
23
+ puts "NOTICE: Notification connection established." if @@notification_announce_reconnect
24
+ while true
25
+ sleep(0.25) # small throttle of requests
26
+ path = '/api/development_plugin_loader/get_notifications'
27
+ path << "?queue=#{@@notification_queue_name}" if @@notification_queue_name
28
+ request = Net::HTTP::Get.new(path)
29
+ setup_request(request)
30
+ # Server uses long-polling, and will respond after a long timeout, or when a notification
31
+ # has been queued for sending to this process.
32
+ response = http.request(request)
33
+ if response.kind_of?(Net::HTTPOK)
34
+ @@notification_queue_name = response['X-Queue-Name']
35
+ begin
36
+ decode_and_handle_notifications response.body
37
+ rescue
38
+ puts "NOTICE: Error handling notification from server."
39
+ end
40
+ else
41
+ raise "Bad response"
42
+ end
43
+ end
44
+ end
45
+
46
+ def self.decode_and_handle_notifications(encoded)
47
+ size = encoded.length
48
+ pos = 0
49
+ while pos < (size - 12)
50
+ type = encoded[pos, 4]
51
+ data_size = encoded[pos + 4, 8].to_i(16)
52
+ data = encoded[pos + 12, data_size]
53
+ pos += 12 + data_size
54
+ handle_notification(type, data)
55
+ end
56
+ end
57
+
58
+ def self.handle_notification(type, data)
59
+ case type
60
+ when 'log '
61
+ # Output from console.log()
62
+ puts "LOG:#{data}"
63
+ else
64
+ puts "WARNING: Unknown notification received from server. Upgrade the plugin tool."
65
+ sleep(5) # throttle problematic responses
66
+ end
67
+ end
68
+
69
+ end
data/lib/packing.rb ADDED
@@ -0,0 +1,59 @@
1
+
2
+ module PluginTool
3
+
4
+ PACKING_ACCEPTABLE_FILENAME = /\A(plugin\.json|(js|static|template)\/[a-z0-9_-]+\.[a-z0-9]+)\Z/
5
+ PACKING_ACCEPTABLE_EXCEPTIONS = ['certificates-temp-http-api.pem']
6
+
7
+ def self.pack_plugin(plugin_name, output_directory)
8
+ # Get filenames and sort
9
+ files = Dir.glob("#{plugin_name}/**/*").map do |filename|
10
+ if File.file? filename
11
+ filename[plugin_name.length+1, filename.length]
12
+ else
13
+ nil
14
+ end
15
+ end .compact.sort
16
+ # Check each filename is acceptable
17
+ files.each do |filename|
18
+ unless filename =~ PACKING_ACCEPTABLE_FILENAME || PACKING_ACCEPTABLE_EXCEPTIONS.include?(filename)
19
+ puts "File '#{filename}' has an unacceptable filename"
20
+ exit 1
21
+ end
22
+ end
23
+ # Clean output directory
24
+ output_plugin_dir = "#{output_directory}/#{plugin_name}"
25
+ puts "Output directory: #{output_plugin_dir}"
26
+ if File.exist? output_plugin_dir
27
+ puts "Removing old output directory #{output_plugin_dir}"
28
+ FileUtils.rm_r(output_plugin_dir)
29
+ end
30
+ # Make file structure
31
+ FileUtils.mkdir(output_plugin_dir)
32
+ ['js','static','template'].each do |subdir|
33
+ FileUtils.mkdir("#{output_plugin_dir}/#{subdir}")
34
+ end
35
+ # Process each file, building a manifest
36
+ puts "Processing files:"
37
+ manifest = ''
38
+ minimiser = PluginTool::Minimiser.new
39
+ files.each do |filename|
40
+ puts " #{filename}"
41
+ data = File.open("#{plugin_name}/#{filename}") { |f| f.read }
42
+ # Minimise file?
43
+ unless filename =~ /\Ajs\//
44
+ data = minimiser.process(data, filename)
45
+ end
46
+ hash = Digest::SHA1.hexdigest(data)
47
+ File.open("#{output_plugin_dir}/#{filename}", "w") { |f| f.write data }
48
+ manifest << "F #{hash} #{filename}\n"
49
+ end
50
+ minimiser.finish
51
+ # Write manifest and version
52
+ File.open("#{output_plugin_dir}/manifest", "w") { |f| f.write manifest }
53
+ version = Digest::SHA1.hexdigest(manifest)
54
+ File.open("#{output_plugin_dir}/version", "w") { |f| f.write "#{version}\n" }
55
+ # All done
56
+ puts "Version: #{version}\nPlugin packed."
57
+ end
58
+
59
+ end
data/lib/plugin.rb ADDED
@@ -0,0 +1,207 @@
1
+
2
+ module PluginTool
3
+
4
+ class Plugin
5
+ def initialize(name, options)
6
+ @name = name
7
+ @options = options
8
+ end
9
+ attr_accessor :name
10
+ attr_accessor :plugin_dir
11
+
12
+ # ---------------------------------------------------------------------------------------------------------
13
+
14
+ def start
15
+ # Check to see if the plugin is valid
16
+ unless File.file?("#{@name}/plugin.json")
17
+ end_on_error "Plugin #{@name} does not exist (no plugin.json file)"
18
+ end
19
+ # Setup for using the plugin
20
+ @plugin_dir = @name
21
+ end_on_error "logic error" unless @plugin_dir =~ /\A[a-zA-Z0-9_-]+\Z/
22
+ @registration_file = "registration.#{@plugin_dir}.yaml"
23
+ puts "Plugin: #{@plugin_dir}"
24
+ end
25
+
26
+ def setup_for_server
27
+ # Make the first empty manifest (may be replaced from server)
28
+ @current_manifest = {}
29
+
30
+ # Get info about the current plugin
31
+ @loaded_plugin_id = nil
32
+ if File.exists? @registration_file
33
+ reg_info = YAML::load(File.open(@registration_file) { |f| f.read })
34
+ @loaded_plugin_id = reg_info[:plugin_id]
35
+ if @loaded_plugin_id != nil
36
+ # Check with server to see if this is still registered, and if so, what files it has on the server
37
+ s_reg_info = PluginTool.get_yaml("/api/development_plugin_loader/manifest/#{@loaded_plugin_id}")
38
+ end_on_error "Couldn't communicate successfully with server." if s_reg_info[:protocol_error]
39
+ if s_reg_info[:result] == 'success'
40
+ @current_manifest = s_reg_info[:manifest]
41
+ else
42
+ puts "NOTICE: Discarding registration information from #{@registration_file}"
43
+ @loaded_plugin_id = nil
44
+ end
45
+ end
46
+ end
47
+
48
+ # If minimisation is active, clear the current manifest so all files are uploaded again.
49
+ if @options.minimiser != nil
50
+ @current_manifest = {}
51
+ end
52
+
53
+ # Flag for registration file save
54
+ registration_file_needs_update = false
55
+
56
+ # If the current plugin registration isn't known, see if the server knows about it
57
+ if @loaded_plugin_id == nil
58
+ s_found_info = PluginTool.post_yaml("/api/development_plugin_loader/find_registration", {:name => @name})
59
+ if s_found_info[:found]
60
+ # Store info returned by the server
61
+ puts "NOTICE: Found existing registration for #{@name} on server. Using it."
62
+ @loaded_plugin_id = s_found_info[:plugin_id]
63
+ @current_manifest = s_found_info[:manifest]
64
+ registration_file_needs_update = true
65
+ end
66
+ end
67
+
68
+ # If there isn't an existing plugin registered, create a new one
69
+ if @loaded_plugin_id == nil
70
+ s_create_info = PluginTool.post_yaml("/api/development_plugin_loader/create")
71
+ end_on_error "Couldn't communicate successfully with server." if s_create_info[:protocol_error]
72
+ end_on_error "Failed to create plugin on server" unless s_create_info[:plugin_id] != nil
73
+ @loaded_plugin_id = s_create_info[:plugin_id]
74
+ registration_file_needs_update = true
75
+ end
76
+
77
+ # Update registration file for next run?
78
+ if registration_file_needs_update
79
+ File.open(@registration_file, 'w') { |f| f.write YAML::dump(:plugin_id => @loaded_plugin_id) }
80
+ end
81
+ end
82
+
83
+ # ---------------------------------------------------------------------------------------------------------
84
+
85
+ def command(cmd)
86
+ case cmd
87
+ when 'license-key'
88
+ application_id = @options.args.first
89
+ if application_id == nil || application_id !~ /\A\d+\Z/
90
+ end_on_error "Numeric application ID must be specified"
91
+ end
92
+ generate_license_key(application_id)
93
+
94
+ when 'pack'
95
+ PluginTool.pack_plugin(@name, @options.output)
96
+
97
+ when 'reset-db'
98
+ puts "Resetting database on server for #{@name}..."
99
+ reset_result = PluginTool.post_yaml("/api/development_plugin_loader/resetdb/#{@loaded_plugin_id}")
100
+ end_on_error "Couldn't remove old database tables" unless reset_result[:result] == 'success'
101
+ apply_result = PluginTool.get_yaml("/api/development_plugin_loader/apply/#{@loaded_plugin_id}")
102
+ end_on_error "Couldn't apply changes" unless apply_result[:result] == 'success'
103
+ puts "Done."
104
+
105
+ when 'uninstall'
106
+ puts "Uninstalling plugin #{@name} from server..."
107
+ reset_result = PluginTool.post_yaml("/api/development_plugin_loader/uninstall/#{@loaded_plugin_id}")
108
+ end_on_error "Couldn't uninstall plugin" unless reset_result[:result] == 'success'
109
+ puts "Done."
110
+
111
+ when 'test'
112
+ puts "Running tests..."
113
+ params = {}
114
+ params[:test] = @options.args.first unless @options.args.empty?
115
+ test_result = PluginTool.post_yaml("/api/development_plugin_loader/run_tests/#{@loaded_plugin_id}", params)
116
+ end_on_error "Couldn't run tests" unless test_result[:result] == 'success'
117
+ puts
118
+ puts test_result[:output] || ''
119
+ puts test_result[:summary] || "(unknown results)"
120
+
121
+ when 'develop'
122
+ # do nothing here
123
+
124
+ else
125
+ end_on_error "Unknown command '#{cmd}'"
126
+
127
+ end
128
+ end
129
+
130
+ # ---------------------------------------------------------------------------------------------------------
131
+
132
+ def develop_setup
133
+ end
134
+
135
+ def develop_scan_and_upload(first_run)
136
+ next_manifest = PluginTool.generate_manifest(@plugin_dir)
137
+ changes = PluginTool.determine_manifest_changes(@current_manifest, next_manifest)
138
+ upload_failed = false
139
+ changes.each do |filename, action|
140
+ filename =~ /\A(.*?\/)?([^\/]+)\Z/
141
+ params = {:filename => $2}
142
+ params[:directory] = $1.gsub(/\/\Z/,'') if $1
143
+ if action == :delete
144
+ puts " #{@name}: Deleting #{filename}"
145
+ PluginTool.post_yaml("/api/development_plugin_loader/delete_file/#{@loaded_plugin_id}", params)
146
+ else
147
+ puts " #{@name}: Uploading #{filename}"
148
+ data = File.open("#{@plugin_dir}/#{filename}") { |f| f.read }
149
+ hash = action
150
+ # Minimise file before uploading?
151
+ if @options.minimiser != nil && filename =~ /\A(static|template)\//
152
+ size_before = data.length
153
+ data = @options.minimiser.process(data, filename)
154
+ size_after = data.length
155
+ hash = Digest::SHA1.hexdigest(data)
156
+ puts " minimisation: #{size_before} -> #{size_after} (#{(size_after * 100) / size_before}%)"
157
+ end
158
+ r = PluginTool.post_yaml("/api/development_plugin_loader/put_file/#{@loaded_plugin_id}", params, {:file => [filename, data]})
159
+ if r[:result] == 'success'
160
+ # If the file was uploaded successfully, but the hash didn't match, abort now
161
+ end_on_error "#{@name}: Disagreed with server about uploaded file hash: local=#{hash}, remote=#{r[:hash]}" unless hash == r[:hash]
162
+ else
163
+ # Otherwise mark as a failed upload to stop an apply operation which will fail
164
+ upload_failed = true
165
+ end
166
+ PluginTool.syntax_check(self, filename) if filename =~ /\.js\Z/
167
+ end
168
+ end
169
+ if upload_failed
170
+ puts "\n#{@name}: Not applying changes due to failure\n\n"
171
+ else
172
+ if !(changes.empty?) || first_run
173
+ puts " #{@name}: Applying changes on server"
174
+ r = PluginTool.get_yaml("/api/development_plugin_loader/apply/#{@loaded_plugin_id}")
175
+ unless r[:result] == 'success'
176
+ puts "\n\n#{@name}: Didn't apply changes on server\n\n"
177
+ PluginTool.beep
178
+ end
179
+ end
180
+ end
181
+ PluginTool.finish_with_connection
182
+ @current_manifest = next_manifest
183
+ end
184
+
185
+ # ---------------------------------------------------------------------------------------------------------
186
+
187
+ def generate_license_key(application_id)
188
+ info = File.open("#{@plugin_dir}/plugin.json") { |f| JSON.parse(f.read) }
189
+ if info["installSecret"] == nil
190
+ end_on_error "#{@name}: No installSecret specified in plugin.json"
191
+ end
192
+ license_key = HMAC::SHA1.sign(info["installSecret"], "application:#{ARGV[1]}")
193
+ puts <<__E
194
+
195
+ Plugin: #{@name}
196
+ Application: #{application_id}
197
+ License key: #{license_key}
198
+ __E
199
+ end
200
+
201
+ def end_on_error(err)
202
+ puts err
203
+ exit 1
204
+ end
205
+ end
206
+
207
+ end