vidispine 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/Gemfile +11 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +296 -0
  6. data/Rakefile +2 -0
  7. data/bin/vidispine +7 -0
  8. data/bin/vidispine-notification-handler +339 -0
  9. data/bin/vidispine-utilities-http-server +13 -0
  10. data/dist/vidispine-1.4.0.gem +0 -0
  11. data/lib/vidispine.rb +5 -0
  12. data/lib/vidispine/api/client.rb +981 -0
  13. data/lib/vidispine/api/client/http_client.rb +291 -0
  14. data/lib/vidispine/api/client/requests.rb +27 -0
  15. data/lib/vidispine/api/client/requests/base_request.rb +234 -0
  16. data/lib/vidispine/api/client/requests/collection_access_add.rb +30 -0
  17. data/lib/vidispine/api/client/requests/collection_access_delete.rb +26 -0
  18. data/lib/vidispine/api/client/requests/collection_metadata_set.rb +24 -0
  19. data/lib/vidispine/api/client/requests/import_placeholder.rb +36 -0
  20. data/lib/vidispine/api/client/requests/import_placeholder_item.rb +38 -0
  21. data/lib/vidispine/api/client/requests/import_sidecar_file.rb +28 -0
  22. data/lib/vidispine/api/client/requests/import_using_uri.rb +44 -0
  23. data/lib/vidispine/api/client/requests/item_access_add.rb +30 -0
  24. data/lib/vidispine/api/client/requests/item_access_delete.rb +26 -0
  25. data/lib/vidispine/api/client/requests/item_delete.rb +17 -0
  26. data/lib/vidispine/api/client/requests/item_export.rb +44 -0
  27. data/lib/vidispine/api/client/requests/item_metadata_get.rb +37 -0
  28. data/lib/vidispine/api/client/requests/item_metadata_set.rb +29 -0
  29. data/lib/vidispine/api/client/requests/item_search.rb +0 -0
  30. data/lib/vidispine/api/client/requests/item_transcode.rb +23 -0
  31. data/lib/vidispine/api/client/requests/items_search.rb +40 -0
  32. data/lib/vidispine/api/client/requests/search.rb +59 -0
  33. data/lib/vidispine/api/client/requests/storage_file_create.rb +23 -0
  34. data/lib/vidispine/api/client/requests/storage_file_get.rb +40 -0
  35. data/lib/vidispine/api/client/requests/storage_files_get.rb +42 -0
  36. data/lib/vidispine/api/utilities.rb +1608 -0
  37. data/lib/vidispine/api/utilities/cli.rb +168 -0
  38. data/lib/vidispine/api/utilities/http_server.rb +230 -0
  39. data/lib/vidispine/api/utilities/http_server/cli.rb +77 -0
  40. data/lib/vidispine/cli.rb +174 -0
  41. data/lib/vidispine/version.rb +3 -0
  42. data/resources/postman/Vidispine API (From WADL v4.11).postman_collection.json +47437 -0
  43. data/vidispine.gemspec +26 -0
  44. metadata +117 -0
@@ -0,0 +1,168 @@
1
+ require 'json'
2
+ require 'pp'
3
+ require 'rexml/document'
4
+
5
+ require 'vidispine/cli'
6
+ require 'vidispine/api/utilities'
7
+
8
+ module Vidispine
9
+
10
+ module API
11
+
12
+ class Utilities
13
+
14
+ class CLI < Vidispine::CLI
15
+
16
+ def self.define_parameters
17
+ default_http_host_address = Vidispine::API::Client::HTTPClient::DEFAULT_HTTP_HOST_ADDRESS
18
+ default_http_host_port = Vidispine::API::Client::HTTPClient::DEFAULT_HTTP_HOST_PORT
19
+ default_accepts_header = Vidispine::API::Client::HTTPClient::DEFAULT_HEADER_ACCEPTS
20
+ default_content_type_header = Vidispine::API::Client::HTTPClient::DEFAULT_HEADER_CONTENT_TYPE
21
+
22
+ argument_parser.on('--host-address HOSTADDRESS', 'The address of the server to communicate with.', "\tdefault: #{default_http_host_address}") { |v| arguments[:http_host_address] = v }
23
+ argument_parser.on('--host-port HOSTPORT', 'The port to use when communicating with the server.', "\tdefault: #{default_http_host_port}") { |v| arguments[:http_host_port] = v }
24
+ argument_parser.on('--username USERNAME', 'The account username to authenticate with.') { |v| arguments[:username] = v }
25
+ argument_parser.on('--password PASSWORD', 'The account password to authenticate with.') { |v| arguments[:password] = v }
26
+
27
+ argument_parser.on('--accept-header VALUE', 'The value for the Accept header sent in each request.', "\tdefault: #{default_accepts_header}") { |v| arguments[:accepts_header] = v }
28
+ argument_parser.on('--content-type VALUE', 'The value for the Content-Type header sent in each request.', "\tdefault: #{default_content_type_header}") { |v| arguments[:content_type] = v }
29
+ argument_parser.on('--method-name METHODNAME', 'The name of the method to call.') { |v| arguments[:method_name] = v }
30
+ argument_parser.on('--method-arguments JSON', 'The arguments to pass when calling the method.') { |v| arguments[:method_arguments] = v }
31
+ argument_parser.on('--storage-map JSON', 'A map of file paths to storage ids to use in utility methods.') { |v| arguments[:storage_map] = v }
32
+ argument_parser.on('--metadata-map JSON', 'A map of field aliases to field names to use in utility methods.') { |v| arguments[:metadata_map] = v }
33
+ argument_parser.on('--pretty-print', 'Will format the output to be more human readable.') { |v| arguments[:pretty_print] = v }
34
+
35
+ argument_parser.on('--log-to FILENAME', 'Log file location.', "\tdefault: #{log_to_as_string}") { |v| arguments[:log_to] = v }
36
+ argument_parser.on('--log-level LEVEL', LOGGING_LEVELS.keys, "Logging level. Available Options: #{LOGGING_LEVELS.keys.join(', ')}",
37
+ "\tdefault: #{LOGGING_LEVELS.invert[arguments[:log_level]]}") { |v| arguments[:log_level] = LOGGING_LEVELS[v] }
38
+
39
+ argument_parser.on('--[no-]options-file [FILENAME]', 'Path to a file which contains default command line arguments.', "\tdefault: #{arguments[:options_file_path]}" ) { |v| arguments[:options_file_path] = v}
40
+ argument_parser.on_tail('-h', '--help', 'Display this message.') { puts help; exit }
41
+ end
42
+
43
+ attr_accessor :logger, :api
44
+
45
+ def after_initialize
46
+ initialize_api(arguments)
47
+ end
48
+
49
+ def initialize_api(args = { })
50
+ @api = Vidispine::API::Utilities.new(args )
51
+ end
52
+
53
+ def run(args = arguments, opts = options)
54
+ storage_map = args[:storage_map]
55
+ @api.default_storage_map = JSON.parse(storage_map) if storage_map.is_a?(String)
56
+
57
+ metadata_map = args[:metadata_map]
58
+ @api.default_metadata_map = JSON.parse(metadata_map) if metadata_map.is_a?(String)
59
+
60
+ method_name = args[:method_name]
61
+ send(method_name, args[:method_arguments], :pretty_print => args[:pretty_print]) if method_name
62
+
63
+ self
64
+ end
65
+
66
+ def send(method_name, method_arguments, params = {})
67
+ method_name = method_name.to_sym
68
+ logger.debug { "Executing Method: #{method_name}" }
69
+
70
+ send_arguments = [ method_name ]
71
+
72
+ if method_arguments
73
+ method_arguments = JSON.parse(method_arguments, :symbolize_names => true) if method_arguments.is_a?(String) and method_arguments.start_with?('{', '[')
74
+ send_arguments.concat method_arguments.is_a?(Array) ? [ *method_arguments ] : [ method_arguments ]
75
+ end
76
+ #puts "Send Arguments: #{send_arguments.inspect}"
77
+ response = api.__send__(*send_arguments)
78
+
79
+ # if response.code.to_i.between?(500,599)
80
+ # puts parsed_response
81
+ # exit
82
+ # end
83
+ #
84
+ # if ResponseHandler.respond_to?(method_name)
85
+ # ResponseHandler.client = api
86
+ # ResponseHandler.response = response
87
+ # response = ResponseHandler.__send__(*send_arguments)
88
+ # end
89
+
90
+ if params[:pretty_print]
91
+ if response.is_a?(String)
92
+ _response_cleaned = response.strip
93
+ if _response_cleaned.start_with?('{', '[')
94
+ puts prettify_json(response)
95
+ elsif _response_cleaned.start_with?('<') and _response_cleaned.end_with?('>')
96
+ puts prettify_xml(response)
97
+ else
98
+ #pp response.is_a?(String) ? response : JSON.pretty_generate(response) rescue response
99
+ puts response.is_a?(String) ? response : JSON.pretty_generate(response) rescue response
100
+ end
101
+ else
102
+ pp response.is_a?(String) ? response : JSON.pretty_generate(response) rescue response
103
+ end
104
+ else
105
+ response = JSON.generate(response) if response.is_a?(Hash) or response.is_a?(Array)
106
+ puts response
107
+ end
108
+ # send
109
+ end
110
+
111
+ def prettify_json(json)
112
+ JSON.pretty_generate(JSON.parse(json))
113
+ end
114
+
115
+ def prettify_xml(xml, options = { })
116
+ document = REXML::Document.new(xml)
117
+ document.write(output = '', options[:indent] || 1)
118
+
119
+ return output unless options.fetch(:collapse_tags, true)
120
+
121
+ last_open_tag = ''
122
+ #last_matching_close_tag = ''
123
+ last_was_tag = false
124
+ last_was_matching_close_tag = false
125
+
126
+ output.lines.map do |v|
127
+ _v = v.strip
128
+
129
+ is_tag = _v.start_with?('<') and _v.end_with?('>')
130
+ is_open_tag = (is_tag and !_v.start_with?('</'))
131
+ is_matching_close_tag = (is_tag and !is_open_tag and _v == "</#{(last_open_tag || '')[1..-1]}")
132
+
133
+ _output = if is_open_tag
134
+ "#{(last_was_tag and !last_was_matching_close_tag) ? "\n" : ''}#{v.rstrip}"
135
+ elsif is_matching_close_tag
136
+ "#{_v}\n"
137
+ elsif !is_tag
138
+ _v
139
+ else
140
+ v
141
+ end
142
+ #puts "V: '#{_v}' IT: #{is_tag} IOT: #{is_open_tag} ICT: #{is_matching_close_tag} LOT: '#{last_open_tag}' LCT: #{last_matching_close_tag} OUTPUT: '#{_output}'"
143
+
144
+ last_was_tag = is_tag
145
+ last_was_matching_close_tag = is_matching_close_tag
146
+ if is_open_tag
147
+ last_open_tag = _v.split(' ').first
148
+ last_open_tag << '>' unless last_open_tag.end_with?('>')
149
+ elsif is_matching_close_tag
150
+ #last_matching_close_tag = _v
151
+ end
152
+
153
+ _output
154
+ end.join('')
155
+ end
156
+
157
+ # CLI
158
+ end
159
+
160
+ # Utilities
161
+ end
162
+
163
+ # API
164
+ end
165
+
166
+ # Vidispine
167
+ end
168
+ def cli; @cli ||= Vidispine::API::Utilities::CLI end
@@ -0,0 +1,230 @@
1
+ require 'pp'
2
+
3
+ require 'sinatra/base'
4
+ require 'vidispine/api/utilities'
5
+
6
+ module Vidispine
7
+
8
+ module API
9
+
10
+ class Utilities
11
+
12
+ class HTTPServer < Sinatra::Base
13
+
14
+ configure do
15
+ enable :show_exceptions
16
+ enable :dump_errors
17
+ #enable :lock # ensure single request concurrency with a mutex lock
18
+ end
19
+
20
+ DEFAULT_ADDRESS_BINDING = 'localhost'
21
+ DEFAULT_PORT = '4567'
22
+
23
+ attr_accessor :global_arguments,
24
+ :metadata_file_path_field_id,
25
+ :relative_file_path_collection_name_position,
26
+ :storage_path_map
27
+
28
+ # @!group Routes
29
+
30
+ get '/collection-by-name/:collection_name' do
31
+ log_request_match(__method__)
32
+ _response = api.dup.collection_get_by_name(params)
33
+ logger.debug { "Response: #{_response}" }
34
+ format_response(_response)
35
+ end
36
+
37
+ post '/collection-file-add-using-path/?' do
38
+ log_request_match(__method__)
39
+ _params = merge_params_from_body
40
+
41
+ #args_out = _params
42
+ args_out = indifferent_hash.merge({
43
+ :storage_path_map => initial_arguments[:storage_path_map],
44
+ :relative_file_path_collection_name_position => initial_arguments[:relative_file_path_collection_name_position],
45
+ :metadata_file_path_field_id => initial_arguments[:metadata_file_path_field_id],
46
+ })
47
+
48
+ args_out.merge!(_params)
49
+ _response = api.dup.collection_file_add_using_path(args_out)
50
+ format_response(_response)
51
+ end
52
+
53
+ # Shows what gems are within scope. Used for diagnostics and troubleshooting.
54
+ get '/gems' do
55
+ stdout_str = `gem list -b`
56
+ stdout_str ? stdout_str.gsub("\n", '<br/>') : stdout_str
57
+ end
58
+
59
+ get '/favicon.ico' do; end
60
+
61
+ get '/:method_name?' do
62
+ logger.debug { "GET #{params.inspect}" }
63
+ method_name = params[:method_name]
64
+ pass unless api.respond_to?(method_name)
65
+ log_request_match(__method__)
66
+ _params = merge_params_from_body
67
+
68
+ _response = api.dup.send(method_name, _params)
69
+ format_response(_response)
70
+ end
71
+
72
+ post '/:method_name?' do
73
+ logger.debug { "POST #{params.inspect}" }
74
+ method_name = params[:method_name]
75
+ pass unless api.respond_to?(method_name)
76
+ log_request_match(__method__)
77
+
78
+ _params = merge_params_from_body
79
+
80
+ _response = api.dup.send(method_name, _params)
81
+ format_response(_response)
82
+ end
83
+
84
+
85
+
86
+ ### CATCH ALL ROUTES BEGIN
87
+ get /.*/ do
88
+ log_request_match(__method__)
89
+ request_to_s.gsub("\n", '<br/>')
90
+ end
91
+
92
+ post /.*/ do
93
+ log_request_match(__method__)
94
+ end
95
+ ### CATCH ALL ROUTES END
96
+
97
+ # @!endgroup Routes
98
+
99
+ def format_response(response, args = { })
100
+ supported_types = %w(application/json application/xml text/xml)
101
+ case request.preferred_type(supported_types)
102
+ when 'application/json'
103
+ content_type :json
104
+ _response = (response.is_a?(Hash) || response.is_a?(Array)) ? JSON.generate(response) : response
105
+ #when 'application/xml', 'text/xml'
106
+ # content_type :xml
107
+ # _response = XmlSimple.xml_out(response, { :root_name => 'response' })
108
+ else
109
+ content_type :json
110
+ _response = (response.is_a?(Hash) || response.is_a?(Array)) ? JSON.generate(response) : response
111
+ end
112
+ _response
113
+ end # output_response
114
+
115
+ def parse_body
116
+ if request.media_type == 'application/json'
117
+ request.body.rewind
118
+ body_contents = request.body.read
119
+ logger.debug { "Parsing: '#{body_contents}'" }
120
+ if body_contents
121
+ json_params = JSON.parse(body_contents)
122
+ return json_params
123
+ end
124
+ end
125
+
126
+ end # parse_body
127
+
128
+ # Will try to convert a body to parameters and merge them into the params hash
129
+ # Params will override the body parameters
130
+ #
131
+ # @params [Hash] _params (params) The parameters parsed from the query and form fields
132
+ def merge_params_from_body(_params = params)
133
+ _params = _params.dup
134
+ _params_from_body = parse_body
135
+ _params = _params_from_body.merge(_params) if _params_from_body.is_a?(Hash)
136
+ indifferent_hash.merge(_params)
137
+ end # merge_params_from_body
138
+
139
+ # @param [Hash] args
140
+ # @option args [Request] :request
141
+ def request_to_s(args = { })
142
+ _request = args[:request] || request
143
+ output = <<-OUTPUT
144
+ ------------------------------------------------------------------------------------------------------------------------
145
+ REQUEST
146
+ Method: #{_request.request_method}
147
+ URI: #{_request.url}
148
+
149
+ Host: #{_request.host}
150
+ Path: #{_request.path}
151
+ Script Name: #{_request.script_name}
152
+ Query String: #{_request.query_string}
153
+ XHR? #{_request.xhr?}
154
+
155
+ Remote
156
+ Host: #{_request.env['REMOTE_HOST']}
157
+ IP: #{_request.ip}
158
+ User Agent: #{_request.user_agent}
159
+ Cookies: #{_request.cookies}
160
+ Accepts: #{_request.accept.inspect}
161
+ Preferred Type: #{_request.preferred_type}
162
+
163
+ Media Type: #{_request.media_type}
164
+ BODY BEGIN:
165
+ #{_request.body.read}
166
+ BODY END.
167
+
168
+ Parsed Parameters:
169
+ #{PP.pp(_request.params, '', 60)}
170
+
171
+ ------------------------------------------------------------------------------------------------------------------------
172
+ OUTPUT
173
+ _request.body.rewind
174
+ output
175
+ end # request_to_s
176
+
177
+ def log_request(route = '')
178
+ return if request.path == '/favicon.ico'
179
+ logger.debug { "\n#{request_to_s}" }
180
+ #puts requests.insert(request_to_hash)
181
+ end # log_request
182
+
183
+ def log_request_match(route)
184
+ log_request(route)
185
+ logger.debug { "MATCHED: #{request.url} -> #{route}\nParsed Parameters: #{params.inspect}" }
186
+ end # log_request_match
187
+
188
+ def self.initialize_logger(args = { })
189
+ logger = args[:logger] ||= Logger.new(args[:log_to] || STDOUT)
190
+ logger.level = args[:log_level] if args[:log_level]
191
+ logger
192
+ end
193
+
194
+ # @param [Hash] args
195
+ # @option args [Logger] :logger
196
+ # @option args [String] :binding
197
+ # @option args [String] :local_port
198
+ def self.init(args = {})
199
+ logger = initialize_logger(args)
200
+ set(:logger, logger)
201
+
202
+ logger.debug { "Initializing HTTP Server. Arguments: #{args.inspect}" }
203
+
204
+ _binding = args.delete(:binding) { DEFAULT_ADDRESS_BINDING }
205
+ _port = args.delete(:port) { DEFAULT_PORT }
206
+ set(:bind, _binding)
207
+ set(:port, _port)
208
+ set(:initial_arguments, args)
209
+
210
+ set(:api, args[:api])
211
+ end
212
+
213
+ def logger
214
+ #self.class.logger
215
+ settings.logger
216
+ end
217
+
218
+ def api
219
+ #self.class.api
220
+ settings.api
221
+ end
222
+
223
+ end
224
+
225
+ end
226
+
227
+ end
228
+
229
+ end
230
+
@@ -0,0 +1,77 @@
1
+ require 'vidispine/cli'
2
+ require 'vidispine/api/utilities/http_server'
3
+
4
+ module Vidispine
5
+
6
+ module API
7
+
8
+ class Utilities
9
+
10
+ class HTTPServer
11
+
12
+ class CLI < Vidispine::CLI
13
+
14
+ def self.define_parameters
15
+ default_http_host_address = Vidispine::API::Client::HTTPClient::DEFAULT_HTTP_HOST_ADDRESS
16
+ default_http_host_port = Vidispine::API::Client::HTTPClient::DEFAULT_HTTP_HOST_PORT
17
+ default_vidispine_username = Vidispine::API::Client::HTTPClient::DEFAULT_USERNAME
18
+ default_vidispine_password = Vidispine::API::Client::HTTPClient::DEFAULT_PASSWORD
19
+
20
+ default_port = Vidispine::API::Utilities::HTTPServer::DEFAULT_PORT
21
+
22
+ argument_parser.on('--vidispine-http-host-address HOSTADDRESS', 'The address of the server to communicate with.', "\tdefault: #{default_http_host_address}") { |v| arguments[:http_host_address] = v }
23
+ argument_parser.on('--vidispine-http-host-port HOSTPORT', 'The port to use when communicating with the server.', "\tdefault: #{default_http_host_port}") { |v| arguments[:http_host_port] = v }
24
+ argument_parser.on('--vidispine-username USERNAME', 'The account username to authenticate with.', "\tdefault: #{default_vidispine_username}") { |v| arguments[:username] = v }
25
+ argument_parser.on('--vidispine-password PASSWORD', 'The account password to authenticate with.', "\tdefault: #{default_vidispine_password}") { |v| arguments[:password] = v }
26
+
27
+ argument_parser.on('--storage-path-map MAP', 'A path=>storage-id mapping to match incoming file paths to storages.') { |v| arguments[:storage_path_map] = v }
28
+ argument_parser.on('--metadata-field-map MAP', 'A path=>portal-field-id mapping to match incoming metadata fields to portal metadata fields.') { |v| arguments[:metadata_map] = v }
29
+ argument_parser.on('--relative-file-path-collection-name-position NUM',
30
+ 'The relative position from the storage base path in which to select the collection name.',
31
+ "\tdefault: 0") { |v| arguments[:relative_file_path_collection_name_position] = v }
32
+ argument_parser.on('--metadata-file-path-field-id ID',
33
+ 'The Id of the metadata field where the file path is to be stored.'
34
+ ) { |v| arguments[:metadata_file_path_field_id] = v }
35
+ argument_parser.on('--bind-to-address ADDRESS', 'The local address to bind the server to.') { |v| arguments[:bind] = v }
36
+ argument_parser.on('--port PORT', 'The port to bind to.', "\tdefault: #{default_port}") { |v| arguments[:port] = v }
37
+ argument_parser.on('--log-to FILENAME', 'Log file location.', "\tdefault: #{log_to_as_string}") { |v| arguments[:log_to] = v }
38
+ argument_parser.on('--log-level LEVEL', LOGGING_LEVELS.keys, "Logging level. Available Options: #{LOGGING_LEVELS.keys.join(', ')}",
39
+ "\tdefault: #{LOGGING_LEVELS.invert[arguments[:log_level]]}") { |v| arguments[:log_level] = LOGGING_LEVELS[v] }
40
+
41
+ argument_parser.on('--[no-]options-file [FILENAME]', 'Path to a file which contains default command line arguments.', "\tdefault: #{arguments[:options_file_path]}" ) { |v| arguments[:options_file_path] = v}
42
+ argument_parser.on_tail('-h', '--help', 'Display this message.') { puts help; exit }
43
+ end
44
+
45
+ attr_accessor :logger, :api, :app
46
+
47
+ def after_initialize
48
+ initialize_api(arguments)
49
+ initialize_app(arguments)
50
+ end
51
+
52
+ def initialize_api(args = { })
53
+ @api = Vidispine::API::Utilities.new(args)
54
+ end
55
+
56
+ def initialize_app(args = { })
57
+ @app = Vidispine::API::Utilities::HTTPServer
58
+ args_out = args
59
+ args_out[:api] = api
60
+ app.init(args)
61
+ end
62
+
63
+ def run(args = arguments, opts = options)
64
+ app.run!
65
+ self
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ def cli; @cli ||= Vidispine::API::Utilities::HTTPServer::CLI end