ubiquity-mediasilo-api-v3 1.0.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 (33) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +100 -0
  6. data/Rakefile +2 -0
  7. data/bin/ubiquity-mediasilo-api-v3 +26 -0
  8. data/lib/ubiquity/cli.rb +127 -0
  9. data/lib/ubiquity/mediasilo/api/v3.rb +11 -0
  10. data/lib/ubiquity/mediasilo/api/v3/cli.rb +94 -0
  11. data/lib/ubiquity/mediasilo/api/v3/client.rb +712 -0
  12. data/lib/ubiquity/mediasilo/api/v3/client/paginator.rb +133 -0
  13. data/lib/ubiquity/mediasilo/api/v3/client/requests.rb +18 -0
  14. data/lib/ubiquity/mediasilo/api/v3/client/requests/asset_copy_to_folder.rb +19 -0
  15. data/lib/ubiquity/mediasilo/api/v3/client/requests/asset_copy_to_project.rb +19 -0
  16. data/lib/ubiquity/mediasilo/api/v3/client/requests/asset_create.rb +25 -0
  17. data/lib/ubiquity/mediasilo/api/v3/client/requests/asset_delete.rb +15 -0
  18. data/lib/ubiquity/mediasilo/api/v3/client/requests/asset_get_by_id.rb +14 -0
  19. data/lib/ubiquity/mediasilo/api/v3/client/requests/asset_move_to_folder.rb +16 -0
  20. data/lib/ubiquity/mediasilo/api/v3/client/requests/asset_move_to_project.rb +16 -0
  21. data/lib/ubiquity/mediasilo/api/v3/client/requests/assets_get.rb +56 -0
  22. data/lib/ubiquity/mediasilo/api/v3/client/requests/assets_get_by_folder_id.rb +14 -0
  23. data/lib/ubiquity/mediasilo/api/v3/client/requests/assets_get_by_project_id.rb +14 -0
  24. data/lib/ubiquity/mediasilo/api/v3/client/requests/base_request.rb +250 -0
  25. data/lib/ubiquity/mediasilo/api/v3/client/requests/batch.rb +20 -0
  26. data/lib/ubiquity/mediasilo/api/v3/client/requests/project_create.rb +15 -0
  27. data/lib/ubiquity/mediasilo/api/v3/client/requests/quicklink_create.rb +54 -0
  28. data/lib/ubiquity/mediasilo/api/v3/client/requests/quicklink_share.rb +32 -0
  29. data/lib/ubiquity/mediasilo/api/v3/http_client.rb +259 -0
  30. data/lib/ubiquity/mediasilo/api/v3/utilities.rb +1145 -0
  31. data/lib/ubiquity/mediasilo/api/v3/version.rb +9 -0
  32. data/ubiquity-mediasilo-api-v3.gemspec +23 -0
  33. metadata +104 -0
@@ -0,0 +1,20 @@
1
+ module Ubiquity::MediaSilo::API::V3::Client::Requests
2
+
3
+ # @see http://docs.mediasilo.com/v3.0/docs/batch-overview
4
+ class Batch < BaseRequest
5
+
6
+ HTTP_METHOD = :post
7
+ HTTP_PATH = 'batch'
8
+ HTTP_SUCCESS_CODE = 200
9
+
10
+ PARAMETERS = [
11
+ { :name => :requests, :send_in => :body, :send_key => false }
12
+ ]
13
+
14
+ def body
15
+ @body ||= arguments[:requests]
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,15 @@
1
+ module Ubiquity::MediaSilo::API::V3::Client::Requests
2
+
3
+ class ProjectCreate < BaseRequest
4
+
5
+ HTTP_METHOD = :post
6
+ HTTP_PATH = '/projects'
7
+
8
+ PARAMETERS = [
9
+ { :name => :name, :required => true },
10
+ :description,
11
+ ]
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,54 @@
1
+ module Ubiquity::MediaSilo::API::V3::Client::Requests
2
+
3
+ class QuicklinkCreate < BaseRequest
4
+
5
+ HTTP_METHOD = :post
6
+ HTTP_PATH = '/quicklinks'
7
+ DEFAULT_PARAMETER_SEND_IN_VALUE = :body
8
+
9
+ PARAMETERS = [
10
+ { :name => :title, :required => true },
11
+ :description,
12
+ :assetIds,
13
+ :configuration,
14
+ :expires,
15
+ :authorizedUserIds,
16
+
17
+ # :settings,
18
+ # :configuration_id,
19
+ # :audience,
20
+ # :playback,
21
+ # :allow_download,
22
+ # :allow_feedback,
23
+ # :show_metadata,
24
+ # :notify_email,
25
+ # :include_directlink,
26
+ # :password
27
+ ]
28
+
29
+ # def post_process_arguments
30
+ # settings = arguments.delete(:settings)
31
+ # audience = arguments.delete(:audience)
32
+ # playback = arguments.delete(:playback)
33
+ # allow_download = arguments.delete(:allow_download)
34
+ # allow_feedback = arguments.delete(:allow_feedback)
35
+ # show_metadata = arguments.delete(:show_metadata)
36
+ # notify_email = arguments.delete(:notify_email)
37
+ # include_directlink = arguments.delete(:include_directlink)
38
+ # password = arguments.delete(:password)
39
+ #
40
+ # configuration = arguments[:configuration] || { }
41
+ # configuration_id = arguments[:configuration_id]
42
+ #
43
+ # # TODO Symbolize Configuration Keys
44
+ # configuration[:id] ||= configuration_id || ''
45
+ #
46
+ # settings ||= configuration[:settings] || { }
47
+ #
48
+ # settings[:audience] = audience
49
+ #
50
+ # end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,32 @@
1
+ module Ubiquity::MediaSilo::API::V3::Client::Requests
2
+
3
+ class QuicklinkShare < BaseRequest
4
+
5
+ HTTP_METHOD = :post
6
+ HTTP_PATH = '/shares'
7
+ DEFAULT_PARAMETER_SEND_IN_VALUE = :body
8
+
9
+ PARAMETERS = [
10
+ { :name => :targetObjectId, :required => true },
11
+ :emailShare,
12
+ :subject,
13
+ :message,
14
+ :audience
15
+ ]
16
+
17
+ def post_process_arguments
18
+ _email_share = arguments[:emailShare]
19
+ _email_share ||= { }
20
+ _audience = arguments[:audience] || _email_share[:audience] || _email_share['audience'] || [ ]
21
+ _subject = arguments[:subject] || _email_share[:subject] || _email_share['subject']
22
+ _message = arguments[:message] || _email_share[:message] || _email_share['message']
23
+ _email_share[:audience] ||= _audience
24
+ _email_share[:subject] ||= _subject if _subject
25
+ _email_share[:message] ||= _message if _message
26
+
27
+ arguments[:emailShare] = _email_share
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,259 @@
1
+ require 'cgi'
2
+ require 'json'
3
+ require 'logger'
4
+ require 'net/http'
5
+ require 'net/https'
6
+
7
+ require 'ubiquity/mediasilo/api/v3'
8
+
9
+ module Ubiquity
10
+ module MediaSilo
11
+ class API
12
+ module V3
13
+
14
+ class HTTPClient
15
+
16
+ class RateLimitException < StandardError; end
17
+
18
+ # Ruby uses all lower case headers but Jetty uses case sensitive headers
19
+ class CaseSensitiveHeaderKey < String
20
+ def downcase; self end
21
+ def capitalize; self end
22
+ end
23
+
24
+
25
+ attr_accessor :logger, :http, :http_host_address, :http_host_port, :base_uri
26
+ attr_accessor :hostname, :username, :password
27
+
28
+ attr_accessor :default_request_headers
29
+
30
+ attr_accessor :log_request_body, :log_response_body, :log_pretty_print_body
31
+
32
+ attr_accessor :request, :response
33
+
34
+ # DEFAULT_HTTP_HOST_ADDRESS = 'api.mediasilo.com'
35
+ DEFAULT_HTTP_HOST_ADDRESS = 'p-api-new.mediasilo.com'
36
+ DEFAULT_HTTP_HOST_PORT = 443
37
+
38
+ def initialize(args = { })
39
+ args = args.dup
40
+ initialize_logger(args)
41
+ initialize_http(args)
42
+
43
+ @hostname = args[:hostname] || ''
44
+ @username = args[:username] || ''
45
+ @password = args[:password] || ''
46
+ @api_key = args[:api_key]
47
+ @base_uri = args[:base_uri] || "http#{http.use_ssl? ? 's' : ''}://#{http.address}:#{http.port}/v3/"
48
+
49
+ @user_agent_default = "#{@hostname}:#{@username} Ubiquity Ruby SDK Version #{Ubiquity::MediaSilo::API::V3::VERSION}"
50
+
51
+ authorization_header_name = CaseSensitiveHeaderKey.new('Authorization')
52
+ authorization_header_value = 'Basic ' + ["#{username}:#{password}"].pack('m').delete("\r\n")
53
+
54
+ host_context_header_name = CaseSensitiveHeaderKey.new('MediaSiloHostContext')
55
+ host_context_header_value = hostname.downcase
56
+
57
+ @default_request_headers = {
58
+ 'user-agent' => @user_agent_default,
59
+ 'Content-Type' => 'application/json; charset=utf-8',
60
+ 'Accept' => 'application/json',
61
+ host_context_header_name => host_context_header_value,
62
+ authorization_header_name => authorization_header_value,
63
+ }
64
+
65
+ if @api_key
66
+ api_key_header_name = CaseSensitiveHeaderKey.new('MediaSiloApiKey')
67
+ default_request_headers[api_key_header_name] = @api_key
68
+ end
69
+
70
+ @log_request_body = args.fetch(:log_request_body, true)
71
+ @log_response_body = args.fetch(:log_response_body, true)
72
+ @log_pretty_print_body = args.fetch(:log_pretty_print_body, true)
73
+
74
+ @delay_between_rate_limit_retries = 300
75
+ @cancelled = false
76
+ @parse_response = args.fetch(:parse_response, true)
77
+ end
78
+
79
+ def initialize_logger(args = { })
80
+ @logger = args[:logger] ||= Logger.new(args[:log_to] || STDOUT)
81
+ log_level = args[:log_level]
82
+ if log_level
83
+ @logger.level = log_level
84
+ args[:logger] = @logger
85
+ end
86
+ @logger
87
+ end
88
+
89
+ def initialize_http(args = { })
90
+ @http_host_address = args[:http_host_address] ||= DEFAULT_HTTP_HOST_ADDRESS
91
+ @http_host_port = args[:http_host_port] ||= DEFAULT_HTTP_HOST_PORT
92
+ @http = Net::HTTP.new(http_host_address, http_host_port)
93
+ http.use_ssl = true
94
+
95
+ # TODO Add SSL Patch
96
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
97
+
98
+ http
99
+ end
100
+
101
+ # Formats a HTTPRequest or HTTPResponse body for log output.
102
+ # @param [HTTPRequest|HTTPResponse] obj
103
+ # @return [String]
104
+ def format_body_for_log_output(obj)
105
+ if obj.content_type == 'application/json'
106
+ if @log_pretty_print_body
107
+ _body = obj.body
108
+ output = "\n" << JSON.pretty_generate(JSON.parse(_body)) rescue _body
109
+ return output
110
+ else
111
+ return obj.body
112
+ end
113
+ else
114
+ return obj.body.inspect
115
+ end
116
+ end
117
+
118
+ # @param [Net::HTTPRequest] request
119
+ def send_request(request)
120
+ @response_parsed = nil
121
+ @request = request
122
+
123
+ begin
124
+ logger.debug { %(REQUEST: #{request.method} http#{http.use_ssl? ? 's' : ''}://#{http.address}:#{http.port}#{request.path} HEADERS: #{request.to_hash.inspect} #{log_request_body and request.request_body_permitted? ? "BODY: #{format_body_for_log_output(request)}" : ''}) }
125
+
126
+ @response = http.request(request)
127
+ logger.debug { %(RESPONSE: #{response.inspect} HEADERS: #{response.to_hash.inspect} #{log_response_body and response.respond_to?(:body) ? "BODY: #{format_body_for_log_output(response)}" : ''}) }
128
+ raise RateLimitException, "#{response.to_hash.inspect}" if response.code == '420'
129
+ rescue RateLimitException => e
130
+ logger.warn { "Rate Limited. Will retry in #{@delay_between_rate_limit_retries} seconds." }
131
+ sleep_break @delay_between_rate_limit_retries
132
+ retry unless @cancelled
133
+ end
134
+
135
+ @parse_response ? response_parsed : response.body
136
+ end
137
+
138
+ def sleep_break(seconds)
139
+ while (seconds > 0)
140
+ sleep(1)
141
+ seconds -= 1
142
+ break if @cancelled
143
+ end
144
+ end
145
+
146
+ def response_parsed
147
+ @response_parsed ||= begin
148
+ response_content_type = response.content_type
149
+ logger.debug { "Parsing Response: #{response_content_type}" }
150
+
151
+ case response_content_type
152
+ when 'application/json'
153
+ JSON.parse(response.body) rescue response
154
+ # when 'text/html'
155
+ # when 'text/plain'
156
+ else
157
+ response.body
158
+ end
159
+ end
160
+ end
161
+
162
+ def build_uri(path = '', query = nil)
163
+ _query = query.is_a?(Hash) ? query.map { |k,v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v)}" }.join('&') : query
164
+ _path = "#{path}#{_query and _query.respond_to?(:empty?) and !_query.empty? ? "?#{_query}" : ''}"
165
+ URI.parse(File.join(base_uri, _path))
166
+ end
167
+
168
+ if RUBY_VERSION.start_with? '1.8.'
169
+ def request_method_name_to_class_name(method_name)
170
+ method_name.to_s.capitalize
171
+ end
172
+ else
173
+ def request_method_name_to_class_name(method_name)
174
+ method_name.to_s.capitalize.to_sym
175
+ end
176
+ end
177
+
178
+ # @param [Symbol] method_name (:get)
179
+ # @param [Hash] args
180
+ # @option args [Hash] :headers ({})
181
+ # @option args [String] :path ('')
182
+ # @option args [Hash] :query ({})
183
+ # @option args [Any] :body (nil)
184
+ # @param [Hash] options
185
+ # @option options [Hash] :default_request_headers (@default_request_headers)
186
+ def call_method(method_name = :get, args = { }, options = { })
187
+ headers = args[:headers] || options[:headers] || { }
188
+ path = args[:path] || ''
189
+ query = args[:query] || { }
190
+ body = args[:body]
191
+
192
+ # Allow the default request headers to be overridden
193
+ _default_request_headers = options.fetch(:default_request_headers, default_request_headers)
194
+ _default_request_headers ||= { }
195
+ _headers = _default_request_headers.merge(headers)
196
+
197
+ @uri = build_uri(path, query)
198
+ klass_name = request_method_name_to_class_name(method_name)
199
+ klass = Net::HTTP.const_get(klass_name)
200
+
201
+ request = klass.new(@uri.request_uri, _headers)
202
+
203
+ if request.request_body_permitted?
204
+ _body = (body and !body.is_a?(String)) ? JSON.generate(body) : body
205
+ logger.debug { "Processing Body: '#{_body}'" }
206
+ request.body = _body if _body
207
+ end
208
+
209
+ send_request(request)
210
+ end
211
+
212
+ def delete(path, options = { })
213
+ query = options.fetch(:query, { })
214
+ @uri = build_uri(path, query)
215
+
216
+
217
+ request = Net::HTTP::Delete.new(@uri.request_uri, default_request_headers)
218
+ body = options[:body]
219
+ if body
220
+ body = JSON.generate(body) unless body.is_a?(String)
221
+ request.body = body
222
+ end
223
+
224
+ send_request(request)
225
+ end
226
+
227
+ def get(path, query = nil, options = { })
228
+ query ||= options.fetch(:query, { })
229
+ @uri = build_uri(path, query)
230
+ request = Net::HTTP::Get.new(@uri.request_uri, default_request_headers)
231
+ send_request(request)
232
+ end
233
+
234
+ def put(path, body, options = { })
235
+ query = options.fetch(:query, { })
236
+ @uri = build_uri(path, query)
237
+ body = JSON.generate(body) unless body.is_a?(String)
238
+
239
+ request = Net::HTTP::Put.new(@uri.request_uri, default_request_headers)
240
+ request.body = body
241
+ send_request(request)
242
+ end
243
+
244
+ def post(path, body, options = { })
245
+ query = options.fetch(:query, { })
246
+ @uri = build_uri(path, query)
247
+ body = JSON.generate(body) unless body.is_a?(String)
248
+
249
+ request = Net::HTTP::Post.new(@uri.request_uri, default_request_headers)
250
+ request.body = body
251
+ send_request(request)
252
+ end
253
+
254
+ end
255
+
256
+ end
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,1145 @@
1
+ require 'ubiquity/mediasilo/api/v3/client'
2
+ require 'open-uri' # for download_file
3
+
4
+ class Exception
5
+ def prefix_message(message_prefix = nil)
6
+ begin
7
+ raise self, "#{message_prefix ? "#{message_prefix} " : ''}#{message}", backtrace
8
+ rescue Exception => e
9
+ return e
10
+ end
11
+ end
12
+ end
13
+
14
+ class Ubiquity::MediaSilo::API::V3::Utilities < Ubiquity::MediaSilo::API::V3::Client
15
+
16
+ # VALID_ASSET_SEARCH_FIELD_NAMES = %w(approvalStatus archiveStatus averageRating commentCount dateCreated dateModified derivatives description external fileName folderId id myRating permissions private progress projectId tags title transcriptStatus type uploadedBy)
17
+ # VALID_ASSET_SEARCH_FIELDS = {
18
+ # :approvalstatus => 'approvalStatus',
19
+ # :archivestatus => 'archiveStatus',
20
+ # :averagerating => 'averageRating',
21
+ # :commentcount => 'commentCount',
22
+ # :datecreated => 'dateCreated',
23
+ # :datemodified => 'dateModified',
24
+ # :derivatives => 'derivatives',
25
+ # :description => 'description',
26
+ # :external => 'external',
27
+ # :filename => 'fileName',
28
+ # :folderid => 'folderId',
29
+ # :id => 'id',
30
+ # :metadatamatch => 'metadatamatch',
31
+ # :myrating => 'myRating', :permissions => 'permissions', :private => 'private', :progress => 'progress', :projectid => 'projectId', :tags => 'tags', :title => 'title', :transcriptstatus => 'transcriptStatus', :type => 'type', :uploadedby => 'uploadedBy' }
32
+
33
+
34
+ def default_case_sensitive_search
35
+ true
36
+ end
37
+
38
+ # Creates an asset building any missing parts of the path (Project/Folder/Asset)
39
+ #
40
+ # Required Parameters
41
+ # :url
42
+ # :mediasilo_path or :file_path
43
+ #
44
+ # Optional Parameters
45
+ # :metadata
46
+ # :overwrite_existing_asset
47
+ # :asset_search_field_name
48
+ def asset_create_using_path(args = { })
49
+
50
+ file_url = args[:url] || args[:file_url] || args[:source_url]
51
+ raise ArgumentError ':url is a required parameter.' unless file_url
52
+
53
+ file_path = args[:mediasilo_path] || args[:file_path]
54
+ raise ArgumentError ':mediasilo_path is a required parameter.' unless file_path
55
+
56
+ ms_metadata = args[:metadata]
57
+
58
+ asset_title = args[:title]
59
+ asset_description = args[:description]
60
+ additional_asset_create_params = { }
61
+ additional_asset_create_params['title'] = asset_title if asset_title
62
+ additional_asset_create_params['description'] = asset_description if asset_description
63
+
64
+ overwrite_existing_asset = args.fetch(:overwrite_existing_asset, false)
65
+ asset_search_field_name = args[:asset_search_field_name] || :filename
66
+
67
+ #logger.info { "Creating Asset on MediaSilo Using File Path: '#{file_path}'. File URL: #{file_url}" }
68
+
69
+ #ms_uuid = ms.asset_create(file_url, { }, ms_project)
70
+ path_create_options = {
71
+ :overwrite_existing_asset => overwrite_existing_asset,
72
+ :additional_asset_create_params => additional_asset_create_params,
73
+ :asset_search_field_name => asset_search_field_name
74
+ }
75
+ begin
76
+ result = path_create(file_path, true, file_url, ms_metadata, path_create_options)
77
+ # rescue => e
78
+ # raise e, "Exception Creating Asset Using File Path. #{e.message}", e.backtrace
79
+ end
80
+
81
+ output_values = { }
82
+ output_values[:result] = result
83
+
84
+ return false unless result and result.has_key?(:asset)
85
+ #return publish_error('Error creating asset.') unless result and result.has_key?(:asset)
86
+
87
+ result_asset = result[:asset]
88
+ if result_asset == false
89
+ ms_asset_id = false
90
+ elsif result[:asset_missing] == false
91
+ ms_asset_id = result[:existing][:asset]['id'] # if not result[:existing][:asset]['uuid'].nil?
92
+ elsif result_asset.is_a?(Array)
93
+
94
+ #Metadata creation failed but asset was created Array(false, "uuid")
95
+ #TODO: HANDLE CASE WHERE METADATA IS NOT CREATED BUT THE ASSET IS
96
+ ms_asset_id = result_asset[1]
97
+
98
+ else
99
+ ms_asset_id = result_asset['id']
100
+ ms_asset_id ||= result_asset
101
+ end
102
+ #Setting metadata during asset_create doesn't work so we set it here
103
+ #response = ms.metadata_create(ms_uuid, ms_metadata) unless ms_metadata.nil?
104
+
105
+ if ms_asset_id
106
+ output_values[:asset_id] = ms_asset_id
107
+ return output_values
108
+ else
109
+ #return publish_error("Error creating asset.\nResponse: #{response}\nMS UUID: #{ms_uuid}\nMS Creating Missing Path Result: #{result}")
110
+ return false
111
+ end
112
+ end
113
+ alias :asset_create_using_file_path :asset_create_using_path
114
+
115
+ def asset_derivatives_transform_to_hash(derivatives)
116
+ return { } unless derivatives.is_a?(Array)
117
+ _derivatives = derivatives.map do |d|
118
+ strategies = d['strategies']
119
+
120
+ d['strategies'] = Hash[strategies.map { |s| [ s['type'], s ] }] if strategies
121
+
122
+ [ d['type'], d ]
123
+ end
124
+ Hash[_derivatives]
125
+ end
126
+
127
+ # Downloads a file from a URI or file location and saves it to a local path
128
+ #
129
+ # @param [String] download_file_path The source path of the file being downloaded
130
+ # @param [String] destination_file_path The destination path for the file being downloaded
131
+ # @param [Boolean] overwrite Determines if the destination file will be overwritten if it is found to exist
132
+ #
133
+ # @return [Hash]
134
+ # * :download_file_path [String] The source path of the file being downloaded
135
+ # * :overwrite [Boolean] The value of the overwrite parameter when the method was called
136
+ # * :file_downloaded [Boolean] Indicates if the file was downloaded, will be false if overwrite was true and the file existed
137
+ # * :destination_file_existed [String|Boolean] The value will be 'unknown' if overwrite is true because the file exist check will not have been run inside of the method
138
+ # * :destination_file_path [String] The destination path for the file being downloaded
139
+ def download_file(download_file_path, destination_file_path, overwrite = false)
140
+ logger.debug { "Downloading '#{download_file_path}' -> '#{destination_file_path}' Overwrite: #{overwrite}" }
141
+ file_existed = 'unknown'
142
+ if overwrite or not(file_existed = File.exists?(destination_file_path))
143
+ File.open(destination_file_path, 'wb') { |tf|
144
+ open(download_file_path) { |sf| tf.write sf.read }
145
+ }
146
+ file_downloaded = true
147
+ else
148
+ file_downloaded = false
149
+ end
150
+ return { :download_file_path => download_file_path, :overwrite => overwrite, :file_downloaded => file_downloaded, :destination_file_existed => file_existed, :destination_file_path => destination_file_path }
151
+ end
152
+
153
+ # @option [String] derivative_name Known options are proxy, proxy2m, source, and waveform
154
+ def asset_download_derivative(derivative_name, asset, destination_file_path, overwrite = false, field_name = 'url')
155
+ return asset.map { |a| asset_download_derivative(derivative_name, a, destination_file_path, overwrite) } if asset.is_a?(Array)
156
+ asset = asset_get_by_id_extended( { :asset_id => asset }, :include_derivatives_hash => true ) if asset.is_a? String
157
+ _derivatives = asset['derivatives_hash'] || begin
158
+ asset_derivatives_transform_to_hash(asset['derivatives'])
159
+ end
160
+
161
+ file_to_download = _derivatives[derivative_name][field_name]
162
+ asset_download_resource(file_to_download, destination_file_path, overwrite)
163
+ end
164
+
165
+ def asset_download_poster_frame_file(asset, destination_file_path, overwrite = false)
166
+ return asset.map { |a| asset_download_poster_frame_file(a, destination_file_path, overwrite) } if asset.is_a?(Array)
167
+ asset = asset_get_by_id(:asset_id => asset ) if asset.is_a? String
168
+
169
+ file_to_download = asset['posterFrame']
170
+ asset_download_resource(file_to_download, destination_file_path, overwrite)
171
+ end
172
+
173
+ # @param [String|Hash] asset
174
+ # @param [String] destination_file_path
175
+ # @param [Boolean] overwrite
176
+ # @return [Hash] see (MediaSilo#download_file)
177
+ def asset_download_proxy_file(asset, destination_file_path, overwrite = false)
178
+ asset_download_derivative('proxy', asset, destination_file_path, overwrite)
179
+ end
180
+
181
+ # @param [String] download_file_path
182
+ # @param [String] destination_file_path
183
+ # @param [Boolean] overwrite
184
+ def asset_download_resource(download_file_path, destination_file_path, overwrite = false)
185
+ # download_file_path = URI.encode(download_file_path)
186
+
187
+ destination_file_path = File.join(destination_file_path, File.basename(URI.decode(download_file_path))) if File.directory? destination_file_path
188
+ download_file(download_file_path, destination_file_path, overwrite)
189
+ end
190
+
191
+ # @param [String|Hash] asset
192
+ # @param [String] destination_file_path
193
+ # @param [Boolean] overwrite
194
+ # @return [Hash] see (MediaSilo#download_file)
195
+ def asset_download_source_file(asset, destination_file_path, overwrite = false)
196
+ asset_download_derivative('source', asset, destination_file_path, overwrite)
197
+ end
198
+
199
+ def asset_download_proxy_poster_frame_file(asset, destination_file_path, overwrite = false)
200
+ asset_download_derivative('proxy', asset, destination_file_path, overwrite, 'posterFrame')
201
+ end
202
+
203
+ def asset_download_proxy_thumbnail_file(asset, destination_file_path, overwrite = false)
204
+ asset_download_derivative('proxy', asset, destination_file_path, overwrite, 'thumbnail')
205
+ end
206
+
207
+
208
+ # @param [String] asset_uuid
209
+ # @param [Hash] args A hash of arguments
210
+ # @option args [String] :asset_id The uuid of the asset to edit
211
+ # @option args [Hash] :metadata The asset's metadata.
212
+ # @option args [Boolean] :mirror_metadata If set to true then the metadata will mirror :metadata which means that any keys existing on MediaSilo that don't exists in :metadata will be deleted from MediaSilo.
213
+ # @option args [Array] :tags_to_add_to_asset An array of tag names to add to the asset
214
+ # @option args [Array] :tags_to_remove_from_asset An array of tag names to remove from the asset
215
+ # @option args [Boolean|Hash|Array] :add_quicklink_to_asset
216
+ # @option args (see MediaSilo#asset_edit)
217
+ def asset_edit_extended(asset_id, args = { }, options = { })
218
+ logger.debug { "Asset Edit Extended: #{asset_id} ARGS: #{args.inspect}" }
219
+ if asset_id.is_a?(Hash)
220
+ options = args.dup
221
+ args = asset_id.dup
222
+ asset_id = args[:asset_id]
223
+ raise ArgumentError, 'Error Editing Asset. Missing required argument :asset_id' unless asset_id
224
+ else
225
+ args = args.dup
226
+ args[:asset_id] = asset_id
227
+ end
228
+
229
+ ms_metadata = args.delete(:metadata) { false }
230
+ mirror_metadata = args.delete(:mirror_metadata) { false }
231
+ #
232
+ add_tag_to_asset = args.delete(:tags_to_add) { [ ] }
233
+ add_tag_to_asset = args.delete(:tags_to_add_to_asset) { add_tag_to_asset }
234
+
235
+ remove_tag_from_asset = args.delete(:tags_to_remove) { [ ] }
236
+ remove_tag_from_asset = args.delete(:tags_to_remove_from_asset) { remove_tag_from_asset }
237
+ #
238
+ # add_quicklink_to_asset = args.delete(:add_quicklink_to_asset) { false }
239
+
240
+ _response = { :success => false }
241
+ if args[:title] or args[:description]
242
+ result = asset_edit(args)
243
+ _response[:asset_edit_result] = result
244
+ _response[:asset_edit_response] = response
245
+ _response[:asset_edit_success] = success?
246
+ unless success?
247
+ _response[:error_message] = "Error Editing Asset. #{error_message}"
248
+ return _response
249
+ end
250
+ end
251
+
252
+ if ms_metadata.is_a?(Hash)
253
+ if mirror_metadata
254
+ result = metadata_mirror(:asset_id => asset_id, :metadata => ms_metadata)
255
+ else
256
+ result = metadata_create_or_update(:asset_id => asset_id, :metadata => ms_metadata)
257
+ end
258
+ _response[:metadata_edit_result] = result
259
+ _response[:metadata_edit_response] = response
260
+ _response[:metadata_edit_success] = success?
261
+ unless success?
262
+ _response[:error_message] = "Error Editing Asset's Metadata. #{error_message}"
263
+ return _response
264
+ end
265
+ end
266
+
267
+ batch_execute do
268
+ unless remove_tag_from_asset.nil? or remove_tag_from_asset.empty?
269
+ [*remove_tag_from_asset].uniq.each { |tag_to_remove| asset_tag_remove(:asset_id => asset_id, :tag => tag_to_remove) if tag_to_remove.is_a?(String); }
270
+ end
271
+ unless add_tag_to_asset.nil? or add_tag_to_asset.empty?
272
+ [*add_tag_to_asset].uniq.each { |tag_to_add| asset_tag_add(:asset_id => asset_id, :tags => tag_to_add) if tag_to_add.is_a?(String) }
273
+ end
274
+ end
275
+
276
+ #
277
+ # if add_quicklink_to_asset
278
+ # add_quicklink_to_asset.is_a?(Hash) ? quicklink_create(asset_id, add_quicklink_to_asset) : quicklink_create(asset_id)
279
+ # _response[:quicklink_create_response] = response.body_parsed
280
+ # _response[:quicklink_create_success] = success?
281
+ # unless success?
282
+ # _response[:error_message] = "Error Adding Quicklink to Asset. #{error_message}"
283
+ # return _response
284
+ # end
285
+ # end
286
+
287
+ _response[:success] = true
288
+ _response
289
+ end
290
+
291
+ # @param [String] search_field_name
292
+ # @param [String] search_value
293
+ # @param [String|nil] project_id
294
+ # @param [String|nil] folder_id
295
+ # @param [Hash] options
296
+ # @option options [Boolean] :limit_to_project_root (true)
297
+ # @return [Array]
298
+ def asset_get_by_field_search(search_field_name, search_value, project_id = nil, folder_id = nil, options = { })
299
+
300
+ path = if folder_id and !folder_id == 0
301
+ limit_to_project_root = false
302
+ "/folders/#{folder_id}/assets"
303
+ elsif project_id
304
+ limit_to_project_root = options.fetch(:limit_to_project_root, true)
305
+ "/projects/#{project_id}/assets"
306
+ else
307
+ limit_to_project_root = false
308
+ '/assets'
309
+ end
310
+
311
+ # _search_field_name = VALID_ASSET_SEARCH_FIELDS[search_field_name.to_s.downcase.to_sym]
312
+ # raise ArgumentError, "The argument for the search_field_name parameter is invalid. '#{search_field_name}' not found in #{VALID_ASSET_SEARCH_FIELDS.values.inspect}" unless _search_field_name
313
+
314
+ _search_field_name = search_field_name.to_s
315
+ search_operator_default = search_value.is_a?(Array) ? 'in' : nil
316
+ search_operator = options[:operator] || search_operator_default
317
+ case search_operator
318
+ when nil
319
+ search_field_query = { _search_field_name => search_value }
320
+ else
321
+ search_field_query = { _search_field_name => %({"#{search_operator}":"#{[*search_value].join(',')}"}) }
322
+ end
323
+
324
+ # query = { :type => '{"in":"video,image,document,archive,audio"}' }.merge((options[:query] || { })).merge(search_field_query)
325
+ query = (options[:query] || { }).merge(search_field_query)
326
+ assets = http_client.get(path, query)
327
+ assets = [ ] if http_client.response.code == '404'
328
+ limit_to_project_root ? assets.delete_if { |v| v['folderId'] } : assets
329
+ end
330
+
331
+ def asset_get_by_id_extended(args = { }, options = { })
332
+ include_metadata = options.delete(:include_metadata)
333
+ include_derivatives_hash = options.delete(:include_derivatives_hash) { true }
334
+ transform_metadata = options.delete(:transform_metadata) { true }
335
+
336
+ asset_id = args[:asset_id]
337
+
338
+ asset = asset_get_by_id({ :asset_id => asset_id }, options)
339
+
340
+ if include_metadata
341
+ md = metadata_get( { :asset_id => asset['id'] }, options )
342
+ md = metadata_transform_to_hash(md) if transform_metadata
343
+ asset['metadata'] = md
344
+ end
345
+
346
+ if include_derivatives_hash
347
+ _derivatives = asset['derivatives']
348
+ asset['derivatives_hash'] = asset_derivatives_transform_to_hash(_derivatives)
349
+ end
350
+
351
+ asset
352
+ end
353
+
354
+ # @param [Hash] args
355
+ # @param [Hash] options
356
+ # @option options [Boolean] :include_metadata
357
+ # @option options [Boolean] :transform_metadata Will cause metadata to be transformed to a simple Hash
358
+ def assets_get_extended(args = { }, options = { })
359
+ include_metadata = options.delete(:include_metadata)
360
+ include_derivatives_hash = options.delete(:include_derivatives_hash) { true }
361
+ transform_metadata = options.delete(:transform_metadata) { true }
362
+
363
+ assets = assets_get(args, options)
364
+
365
+ assets.map! do |a|
366
+
367
+ if include_metadata
368
+ md = metadata_get(:asset_id => a['id'])
369
+ md = metadata_transform_to_hash(md) if transform_metadata
370
+ a['metadata'] = md
371
+ end
372
+
373
+ if include_derivatives_hash
374
+ _derivatives = a['derivatives'].map do |d|
375
+ strategies = d['strategies']
376
+
377
+ d['strategies'] = Hash[strategies.map { |s| [s['type'], s] }] if strategies
378
+
379
+ [ d['type'], d ]
380
+ end
381
+
382
+ a['derivatives_hash'] = Hash[_derivatives]
383
+ end
384
+
385
+ a
386
+ end if (include_metadata || include_derivatives_hash)
387
+
388
+ assets
389
+ end
390
+
391
+
392
+
393
+ # Refines asset_get_by_field_search results to exact (full string case sensitive) matches
394
+ #
395
+ # @param [String] search_value
396
+ # @param [String] field_name The field to search.
397
+ # ["approvalstatus", "archivestatus", "averagerating", "datecreated", "datemodified", "description",
398
+ # "duration", "external", "filename", "height", "progress", "rating", "secure", "size", "thumbnail_large",
399
+ # "thumbnail_small", "title", "totalcomments", "transcriptstatus", "type", "uploaduser", "uuid", "width"]
400
+ # @param [Array] assets The assets as returned by asset_adavanced_search_by_results
401
+ # @param [Hash] options
402
+ # @option options [Boolean] :return_first_match (false)
403
+ # @option options [Boolean] :match_full_string (true)
404
+ # @option options [Boolean] :case_sensitive (#default_case_sensitive_search)
405
+ def refine_asset_get_by_field_search_results(field_name, search_value, assets, options)
406
+ return unless assets
407
+ return_first_match = options.fetch(:return_first_match, false)
408
+ match_full_string = options.fetch(:match_full_string, true)
409
+ case_sensitive = options.fetch(:case_sensitive, default_case_sensitive_search)
410
+ search_value = search_value.to_s.downcase unless case_sensitive
411
+ method = return_first_match ? :drop_while : :delete_if
412
+ assets.send(method) do |asset|
413
+ asset_value = case_sensitive ? asset[field_name] : asset[field_name].to_s.downcase
414
+ nomatch = match_full_string ? (asset_value != search_value) : (!asset_value.include?(search_value))
415
+ #logger.debug "COMPARING: '#{search_value}' #{nomatch ? '!' : '='}= '#{asset_value}'"
416
+ nomatch
417
+ end if assets and (match_full_string or case_sensitive)
418
+ return assets.first if assets and return_first_match
419
+ assets
420
+ end
421
+
422
+ def asset_get_by_filename(asset_name, project_id = nil, folder_id = nil, options = { })
423
+ assets = asset_get_by_field_search(:filename, asset_name, project_id, folder_id, options)
424
+ refine_asset_get_by_field_search_results('fileName', asset_name, assets, options)
425
+ end
426
+
427
+ def asset_get_by_title(asset_name, project_id = nil, folder_id = nil, options = { })
428
+ assets = asset_get_by_field_search(:title, asset_name, project_id, folder_id, options)
429
+ refine_asset_get_by_field_search_results('title', asset_name, assets, options)
430
+ end
431
+
432
+ def folder_get_by_name(project_id, folder_name, parent_id, options = { })
433
+ logger.debug { "Searching For Folder by Name: '#{folder_name}' Project Id: '#{project_id}' Parent Id: '#{parent_id}'"}
434
+ case_sensitive = options.fetch(:case_sensitive, default_case_sensitive_search)
435
+ return_all_matches = !options.fetch(:return_first_match, false)
436
+
437
+ folders = options[:folders] || (parent_id ? folders_get_by_parent_id(:parent_id => parent_id) : folders_get_by_project_id(:project_id => project_id))
438
+ return false unless folders
439
+
440
+ # Use delete_if instead of keep_if to be ruby 1.8 compatible
441
+ folders.dup.delete_if do |folder|
442
+ folder_name_to_test = folder['name']
443
+ folder_name_to_test.upcase! unless case_sensitive
444
+ no_match = (folder_name_to_test != folder_name)
445
+ return folder unless no_match or return_all_matches
446
+ no_match
447
+ end
448
+ return nil unless return_all_matches
449
+ folders
450
+ end
451
+
452
+ # # A metadata creation utility method that checks for existing keys to avoid duplication
453
+ # #
454
+ # # @param [String] asset_id
455
+ # # @param [Hash] metadata
456
+ # # @return [Boolean]
457
+ # def metadata_create_if_not_exists(asset_id, metadata)
458
+ # return asset_id.map { |au| metadata_create_if_not_exists(au, metadata) } if asset_id.is_a?(Array)
459
+ # return unless metadata.is_a?(Hash) and !metadata.empty?
460
+ #
461
+ # md_existing = metadata_get_by_asset_uuid(asset_id)
462
+ # md_existing_keys = {}
463
+ # md_existing.each do |ms_md|
464
+ # md_existing_keys[ms_md['key']] = ms_md
465
+ # end if md_existing.is_a?(Array)
466
+ #
467
+ # md_to_create = [ ]
468
+ # md_to_edit = [ ]
469
+ #
470
+ # metadata.each do |key, value|
471
+ # if md_existing_keys.has_key?(key)
472
+ # md_current = md_existing_keys[key]
473
+ # md_to_edit << { 'key' => key, 'value' => value } unless (md_current['value'] == value)
474
+ # else
475
+ # md_to_create << { 'key' => key, 'value' => value }
476
+ # end
477
+ # end
478
+ #
479
+ # # logger.debug { <<-MD
480
+ # #
481
+ # # Metadata
482
+ # #
483
+ # # Create
484
+ # # #{PP.pp(md_to_create, '')}
485
+ # #
486
+ # # Edit
487
+ # # #{PP.pp(md_to_edit, '')}
488
+ # # MD
489
+ # # }
490
+ #
491
+ # batch_execute do
492
+ # #md_to_edit.each { |data| metadata_update(data.merge({ :asset_id => asset_id })) }
493
+ # #md_to_create.each { |data| next unless data and !data.empty?; metadata_create(data.merge({ :asset_id => asset_id })) }
494
+ #
495
+ # md_to_edit.each_slice(50) { |data| metadata_update(:asset_id => asset_id, :metadata => data) }
496
+ # md_to_create.each_slice(50) { |data| logger.debug { "Data: #{data.inspect}" }; metadata_create(:asset_id => asset_id, :metadata => data) }
497
+ #
498
+ # end
499
+ #
500
+ # end
501
+
502
+ # # A method that mirrors the metadata passed to the asset, deleting any keys that don't exist in the metadata param
503
+ # #
504
+ # # @param [String] asset_id
505
+ # # @param [Hash] metadata
506
+ # def metadata_mirror(asset_id, metadata = { })
507
+ # return asset_id.map { |au| metadata_mirror(au, metadata) } if asset_id.is_a?(Array)
508
+ #
509
+ # md_to_delete = [ ]
510
+ # md_existing_keys = { }
511
+ #
512
+ # md_existing = metadata_get_by_asset_uuid(asset_id)
513
+ # md_existing.each do |ms_md|
514
+ # ms_md_key = ms_md['key']
515
+ # if metadata.key?(ms_md_key)
516
+ # md_existing_keys[ms_md_key] = ms_md
517
+ # else
518
+ # md_to_delete << ms_md_key
519
+ # end
520
+ # end
521
+ #
522
+ # md_to_create = [ ]
523
+ # md_to_edit = [ ]
524
+ #
525
+ # metadata.each do |key, value|
526
+ # if md_existing_keys.has_key?(key)
527
+ # md_current = md_existing_keys[key]
528
+ # md_to_edit << { 'key' => key, 'value' => value } unless (md_current['value'] == value)
529
+ # else
530
+ # md_to_create << { 'key' => key, 'value' => value }
531
+ # end
532
+ # end
533
+ #
534
+ # batch_execute do
535
+ # md_to_edit.each { |data| metadata_update(data.merge({ :asset_id => asset_id })) }
536
+ # md_to_create.each { |data| metadata_create(data.merge({ :asset_id => asset_id })) }
537
+ # md_to_delete.each { |data| metadata_delete({ :asset_id => asset_id, :metadata_key => data }) }
538
+ # end
539
+ #
540
+ # end
541
+
542
+ # @param [Array] metadata_in
543
+ def metadata_transform_to_hash(metadata_in)
544
+ case metadata_in
545
+ when Array
546
+ # This is what we want
547
+ return Hash[ metadata_in.map { |m| [m['key'], m['value'] ] } ]
548
+ when Hash
549
+ return metadata_in
550
+ else
551
+ return { }
552
+ end
553
+ end
554
+
555
+ # Checks to see if a project/folder/asset path exists and records each as existing or missing
556
+ #
557
+ # @param [String] path The path to be checked for existence. Format: project[/folder][/filename]
558
+ # @param [Boolean] path_contains_asset (false) Indicates that the path contains an asset filename
559
+ # @param [Hash] options ({ })
560
+ # @option options [Boolean] :asset_name_field (:filename)
561
+ # (@see #resolve_path)
562
+ def path_check(path, path_contains_asset = false, options = { })
563
+ return false unless path
564
+
565
+ # Remove any and all instances of '/' from the beginning of the path
566
+ path = path[1..-1] while path.start_with? '/'
567
+
568
+ path_ary = path.split('/')
569
+
570
+ existing_path_result = resolve_path(path, path_contains_asset, options)
571
+ existing_path_ary = existing_path_result[:id_path_ary]
572
+ check_path_length = path_ary.length
573
+
574
+ # Get a count of the number of elements which were found to exist
575
+ existing_path_length = existing_path_ary.length
576
+
577
+ # Drop the first n elements of the array which corresponds to the number of elements found to be existing
578
+ missing_path = path_ary.drop(existing_path_length)
579
+ # In the following logic tree the goal is indicate what was searched for and what was found. If we didn't search
580
+ # for the component (folder/asset) then we don't want to set the missing indicator var
581
+ # (folder_missing/asset_missing) for component as a boolean but instead leave it nil.
582
+ missing_path_length = missing_path.length
583
+ if missing_path_length > 0
584
+ # something is missing
585
+
586
+ if missing_path_length == check_path_length
587
+ # everything is missing in our path
588
+
589
+ project_missing = true
590
+ if path_contains_asset
591
+ # we are missing everything and we were looking for an asset so it must be missing
592
+ asset_missing = true
593
+
594
+ if check_path_length > 2
595
+ #if we were looking for more than two things (project, folder, and asset) and we are missing everything then folders are missing also
596
+ searched_folders = true
597
+ folder_missing = true
598
+ else
599
+
600
+ #if we are only looking for 2 things then that is only project and asset, folders weren't in the path so we aren't missing them
601
+ searched_folders = false
602
+ folder_missing = false
603
+ end
604
+ else
605
+ if check_path_length > 1
606
+ # If we are looking for more than one thing then it was project and folder and both are missing
607
+ searched_folders = true
608
+ folder_missing = true
609
+ else
610
+ searched_folders = false
611
+ folder_missing = false
612
+ end
613
+ end
614
+ else
615
+ #we have found at least one thing and it starts with project
616
+ project_missing = false
617
+ if path_contains_asset
618
+ #missing at least 1 and the asset is at the end so we know it's missing
619
+ asset_missing = true
620
+ if missing_path_length == 1
621
+ #if we are only missing one thing and it's the asset then it's not a folder!
622
+ folder_missing = false
623
+ searched_folders = check_path_length > 2
624
+ else
625
+ # missing_path_length is more than 1
626
+ if check_path_length > 2
627
+ #we are looking for project, folder, and asset and missing at least 3 things so they are all missing
628
+ searched_folders = true
629
+ folder_missing = true
630
+ else
631
+ #we are only looking for project and asset so no folders are missing
632
+ searched_folders = false
633
+ folder_missing = false
634
+ end
635
+ end
636
+ else
637
+ #if we are missing something and the project was found and there was no asset then it must be a folder
638
+ searched_folders = true
639
+ folder_missing = true
640
+ end
641
+ end
642
+ else
643
+ searched_folders = !existing_path_result[:folders].empty?
644
+ project_missing = folder_missing = asset_missing = false
645
+ end
646
+
647
+ {
648
+ :check_path_ary => path_ary,
649
+ :existing => existing_path_result,
650
+ :missing_path => missing_path,
651
+ :searched_folders => searched_folders,
652
+ :project_missing => project_missing,
653
+ :folder_missing => folder_missing,
654
+ :asset_missing => asset_missing,
655
+ }
656
+ end
657
+ alias :check_path :path_check
658
+
659
+
660
+ # Calls check_path to see if any part of a project/folder/asset path are missing from MediaSilo and creates any part that is missing
661
+ #
662
+ # @param [String] path The path to create inside of MediaSilo
663
+ # @param [Boolean] contains_asset see #path_resolve
664
+ # @param [String|nil] asset_url
665
+ # @param [Hash|nil] metadata
666
+ # @param [Hash] options
667
+ # @option options [Hash] :additional_asset_create_params Additional arguments to pass to the asset_create call
668
+ # @option options [Boolean] :overwrite_asset Will cause the asset to be deleted and recreated
669
+ # @option options [Boolean] :path_creation_delay A delay between folder_create calls to allow
670
+ # @return [Hash]
671
+ # {
672
+ # :check_path_ary=>["create_missing_path_test"],
673
+ # :existing=>{
674
+ # :name_path=>"/",
675
+ # :id_path=>"/",
676
+ # :name_path_ary=>[],
677
+ # :id_path_ary=>[],
678
+ # :project=>false,
679
+ # :asset=>nil,
680
+ # :folders=>[]
681
+ # },
682
+ # :missing_path=>[],
683
+ # :searched_folders=>false,
684
+ # :project_missing=>true,
685
+ # :folder_missing=>false,
686
+ # :asset_missing=>nil,
687
+ # :project=>{
688
+ # "id"=>30620,
689
+ # "datecreated"=>"June, 05 2013 15:20:15",
690
+ # "description"=>"",
691
+ # "uuid"=>"15C84A5F-B2D9-0E2F-507D94189F8A1FDC",
692
+ # "name"=>"create_missing_path_test"
693
+ # },
694
+ # :project_id=>30620,
695
+ # :parent_folder_id=>0
696
+ # }
697
+ def path_create(path, contains_asset = false, asset_url = nil, metadata = nil, options = { })
698
+ overwrite_asset = options.fetch(:overwrite_asset, false)
699
+ additional_asset_create_params = options[:additional_asset_create_params] || { }
700
+ path_creation_delay = options[:path_creation_delay] || 10
701
+ asset_search_field_name = options[:asset_search_field_name] || :filename
702
+
703
+ cp_result = check_path(path, contains_asset, :asset_search_field_name => asset_search_field_name)
704
+ logger.debug { "CHECK PATH RESULT #{cp_result.inspect}" }
705
+ return false unless cp_result
706
+
707
+ project_missing = cp_result[:project_missing]
708
+ folder_missing = cp_result[:folder_missing]
709
+ asset_missing = cp_result[:asset_missing]
710
+
711
+ existing = cp_result[:existing]
712
+
713
+ asset = existing[:asset]
714
+
715
+ # This was meant as a Bypass if nothing needed to be done, complicated code maintenance
716
+ # unless project_missing or folder_missing or asset_missing or (!asset_missing and overwrite_asset)
717
+ # project_id = cp_result[:existing][:id_path_ary].first
718
+ # asset = cp_result[:existing][:asset]
719
+ # if contains_asset
720
+ # asset_id = cp_result[:existing][:id_path_ary].last
721
+ # parent_folder_id = cp_result[:existing][:id_path_ary].fetch(-2)
722
+ #
723
+ # asset_edit_extended(asset_id, additional_asset_create_params) if metadata and !metadata.empty?
724
+ # # metadata_create_if_not_exists(asset_id, metadata) if metadata and !metadata.empty?
725
+ # else
726
+ # asset_id = nil
727
+ # parent_folder_id = cp_result[:existing][:id_path_ary].last
728
+ # end
729
+ #
730
+ # result = cp_result.merge({ :project_id => project_id, :parent_folder_id => parent_folder_id, :asset_id => asset_id, :asset => asset })
731
+ # logger.debug { "Create Missing Path Result: #{result.inspect}" }
732
+ # return result
733
+ # end
734
+ searched_folders = cp_result[:searched_folders]
735
+
736
+ missing_path = cp_result[:missing_path]
737
+
738
+ project_name = cp_result[:check_path_ary][0]
739
+ #logger.debug "PMP: #{missing_path}"
740
+ if project_missing
741
+ logger.debug { "Missing Project - Creating Project '#{project_name}'" }
742
+ project = project_create(project_name)
743
+ raise "Error Creating Project. Response: #{project}" unless project.is_a?(Hash)
744
+
745
+ cp_result[:project] = project
746
+ project_id = project['id']
747
+ missing_path.shift
748
+ logger.debug { "Created Project '#{project_name}' - #{project_id}" }
749
+ else
750
+ project_id = existing[:id_path_ary].first
751
+ end
752
+
753
+ if searched_folders
754
+ if folder_missing
755
+ # logger.debug "FMP: #{missing_path}"
756
+
757
+ parent_folder_id = (existing[:id_path_ary].length <= 1) ? 0 : existing[:id_path_ary].last
758
+
759
+ asset_name = missing_path.pop if contains_asset
760
+
761
+ previous_missing = project_missing
762
+ missing_path.each do |folder_name|
763
+ sleep path_creation_delay if path_creation_delay and previous_missing
764
+ begin
765
+ logger.debug { "Creating folder '#{folder_name}' parent id: #{parent_folder_id} project id: #{project_id}" }
766
+ new_folder = folder_create(:name => folder_name, :project_id => project_id, :parent_id => parent_folder_id)
767
+ raise "Error Creating Folder. Response: #{new_folder}" unless new_folder.is_a?(Hash)
768
+
769
+ logger.debug { "New Folder: #{new_folder.inspect}" }
770
+ parent_folder_id = new_folder['id']
771
+ logger.debug { "Folder Created #{new_folder} - #{parent_folder_id}" }
772
+ rescue => e
773
+ raise e.prefix_message("Failed to create folder '#{folder_name}' parent id: '#{parent_folder_id}' project id: '#{project_id}'. Exception:")
774
+ end
775
+ previous_missing = true
776
+ end
777
+
778
+ else
779
+ if contains_asset and not asset_missing
780
+ parent_folder_id = existing[:id_path_ary].fetch(-2)
781
+ else
782
+ parent_folder_id = existing[:id_path_ary].last
783
+ end
784
+ end
785
+ else
786
+ parent_folder_id = 0
787
+ end
788
+
789
+ if contains_asset
790
+ additional_asset_create_params = { } unless additional_asset_create_params.is_a?(Hash)
791
+ additional_asset_create_params['folderId'] = parent_folder_id
792
+ additional_asset_create_params['projectId'] = project_id
793
+ additional_asset_create_params[:metadata] = metadata
794
+ additional_asset_create_params[:source_url] = asset_url
795
+
796
+ if asset_missing
797
+ asset = asset_create(additional_asset_create_params)
798
+ raise "Error Creating Asset: #{asset.inspect} Args: #{additional_asset_create_params.inspect}" unless success?
799
+ else
800
+ if overwrite_asset
801
+ asset_id = existing[:id_path_ary].last
802
+ begin
803
+ raise "Error Message: #{error_message}" unless asset_delete(asset_id)
804
+ rescue => e
805
+ raise e.prefix_message("Error Deleting Existing Asset. Asset ID: #{asset_id} Exception: ")
806
+ end
807
+ asset = asset_create(additional_asset_create_params)
808
+ raise "Error Creating Asset: #{asset.inspect} Args: #{additional_asset_create_params.inspect}" unless success?
809
+ end
810
+ end
811
+
812
+ additional_asset_create_params = additional_asset_create_params.delete_if { |k,v| asset[k] == v }
813
+ asset_edit_extended(asset['id'], additional_asset_create_params) unless additional_asset_create_params.empty?
814
+ # metadata_create_if_not_exists(asset['id'], metadata) if metadata and !metadata.empty?
815
+
816
+ cp_result[:asset] = asset
817
+ end
818
+ result = cp_result.merge({ :project_id => project_id, :parent_folder_id => parent_folder_id })
819
+ logger.debug { "Create Missing Path Result: #{result.inspect}" }
820
+ return result
821
+ end
822
+ alias :create_path :path_create
823
+
824
+ def path_delete(path, options = { })
825
+ raise_exception_on_error = options.fetch(:raise_exception_on_error, true)
826
+ recursive = (options.fetch(:recursive, false) === true) ? true : false
827
+ include_assets = (options.fetch(:include_assets, false) === true) ? true : false
828
+
829
+ path = path[1..-1] if path.start_with? '/' # Remove the leading slash if it is present
830
+ path_ary = path.split('/') # Turn the path into an array of names
831
+
832
+ raise ArgumentError, 'Path is empty. Nothing to do.' if path_ary.empty?
833
+
834
+ if path_ary.last == '*'
835
+ path_ary.pop
836
+
837
+ if path_ary.empty?
838
+ delete_all_projects = options.fetch(:delete_all_projects)
839
+ raise ArgumentError, 'Wildcard Project Deletion is not Enabled.' unless (delete_all_projects === true)
840
+
841
+ projects = projects_get
842
+ return projects.map { |project| path_delete(project['name'], options)}
843
+ end
844
+
845
+ delete_contents_only = true
846
+ else
847
+ delete_contents_only = options.fetch(:delete_contents_only, false)
848
+ end
849
+
850
+ result = check_path(path_ary.join('/'))
851
+ raise "Error checking path. '#{error_message}'" unless result
852
+
853
+ existing_path = result[:existing][:id_path_ary]
854
+ missing_path = result[:missing_path]
855
+
856
+ #The path was not found
857
+ raise "Path not found. Path: '#{path}' Check Path Result: #{result.inspect}" unless missing_path.empty?
858
+
859
+ id_path_ary = existing_path
860
+
861
+ project_id = id_path_ary.shift # Pull the project_id out of the beginning of the array
862
+
863
+ if id_path_ary.empty?
864
+ folder_id = 0
865
+ else
866
+ folder_id = id_path_ary.last
867
+ end
868
+
869
+ path_delete_by_id(project_id, folder_id, recursive, include_assets, delete_contents_only, options)
870
+ rescue ArgumentError, RuntimeError => e
871
+ raise e if raise_exception_on_error
872
+ return false
873
+ end
874
+
875
+ # Deletes a project's and/or folder's contents.
876
+ #
877
+ # @param [String|Integer] project_id The id of the project that you wish to delete.
878
+ # @param [String|Integer] folder_id (0) The parent_folder in the project that you would want to delete the
879
+ # contents of. Defaults to 0 which is the root folder of the project.
880
+ # @param [Boolean] recursive (false) Tells the method to recurse into any sub-folders. Usually you would want
881
+ # this to be true, but the default requires you to be explicit about wanting to delete sub-folders so the
882
+ # default is false.
883
+ # @param [Boolean] include_assets (false) Tells the method to delete any assets in any directory that it
884
+ # traverses into. Usually you would want this to be true, but the default requires you to be explicit about
885
+ # wanting to delete assets so the default is false
886
+ # @param [Boolean] delete_contents_only (true) Tells the method to not delete the parent object
887
+ # (project or folder) unless this is set to true.
888
+ # @param [Hash] options
889
+ # @option option [Boolean] :dry_run
890
+ # @option option [Boolean] :delete_assets_only Will only delete assets along the path but will leave the project
891
+ # and folders in place
892
+ def path_delete_by_id(project_id, folder_id = 0, recursive = false, include_assets = false, delete_contents_only = true, options = { })
893
+ dry_run = options.fetch(:dry_run, false)
894
+ delete_assets_only = options.fetch(:delete_assets_only, false)
895
+
896
+ raise ArgumentError, 'include_assets must be true to use the delete_assets_only option.' if delete_assets_only and !include_assets
897
+
898
+ @logger.debug { "Deleting Path By ID - Project ID: #{project_id} Folder ID: #{folder_id} Recursive: #{recursive} Include Assets: #{include_assets} Delete Contents Only: #{delete_contents_only} Options: #{options.inspect}" }
899
+
900
+ if folder_id and folder_id != 0
901
+ folders = folders_get_by_parent_id(folder_id) || [ ]
902
+ else
903
+ folders = folders_get_by_project_id(project_id) || [ ]
904
+ end
905
+ folders = [ ] unless folders.is_a?(Array)
906
+ if recursive
907
+ folders = [ ] if folders.is_a?(String)
908
+ total_folders = folders.length
909
+ folder_counter = 0
910
+ folders.delete_if do |folder|
911
+ folder_counter += 1
912
+
913
+ @logger.debug { "Deleting Contents of Folder #{folder_counter} of #{total_folders} - #{folder}" }
914
+
915
+ # Pass delete_assets_only as the delete_contents_only argument. This way if we aren't deleting assets only then
916
+ # sub-folders and assets will get deleted recursively, otherwise only assets will be deleted
917
+ path_delete_by_id(project_id, folder['id'], recursive, include_assets, delete_assets_only, options)
918
+ end
919
+ end
920
+
921
+
922
+ if include_assets
923
+ if folder_id == 0
924
+ assets = assets_get_by_project_id(:id => project_id)
925
+ else
926
+ assets = assets_get_by_folder_id(:id => folder_id)
927
+ end
928
+ assets = [ ] unless assets.is_a?(Array)
929
+ total_assets = assets.length
930
+ asset_counter = 0
931
+
932
+ # batch_execute {
933
+ # assets.each { |asset| asset_delete(asset['id']) }
934
+ # }
935
+
936
+ response = assets.map { |asset| asset_delete(asset['id']) }
937
+
938
+ # if dry_run
939
+ # assets = [ ]
940
+ # else
941
+ # assets = response.dup.delete_if { |r| %w(204 404).include?(r['httpStatus']) } unless assets.empty?
942
+ # end
943
+
944
+ assets = [ ]
945
+
946
+ # assets.delete_if { |asset|
947
+ # asset_counter += 1
948
+ #
949
+ # dry_run ? true : asset_delete(asset['id'])
950
+ # }
951
+ else
952
+ assets = [ ] # make assets.empty? pass later on. let ms throw an error if the project/folder isn't empty
953
+ end
954
+
955
+ unless delete_contents_only or delete_assets_only
956
+ if folders.empty? and assets.empty?
957
+ if folder_id === 0
958
+ @logger.debug { "Deleting Project #{project_id}" }
959
+ return ( dry_run ? true : project_delete(project_id) )
960
+ else
961
+ @logger.debug { "Deleting Folder #{folder_id}" }
962
+ return ( dry_run ? true : folder_delete(folder_id) )
963
+ end
964
+ else
965
+ return true if dry_run
966
+ warn "Assets remaining in project/folder: #{project_id}/#{folder_id} : Assets: #{assets.inspect}" unless assets.empty?
967
+ warn "Folders remaining in project/folder: #{project_id}/#{folder_id} : Folders: #{folders.inspect}" unless folders.empty?
968
+ return false
969
+ end
970
+ end
971
+
972
+ return true
973
+ end
974
+
975
+
976
+ def project_get_by_name(args = { }, options = { })
977
+ project_name = case args
978
+ when String; args
979
+ when Hash; args[:name] || args[:project_name]
980
+ end
981
+
982
+ case_sensitive = options.fetch(:case_sensitive, default_case_sensitive_search)
983
+ return_all_matches = !options.fetch(:return_first_match, false)
984
+
985
+
986
+ projects = options[:projects] || projects_get
987
+ logger.debug { "Searching Projects: #{projects.inspect}" }
988
+ return false unless projects
989
+
990
+ project_name.upcase! unless case_sensitive
991
+
992
+ # Use delete_if instead of keep_if to be ruby 1.8 compatible
993
+ projects = projects.dup.delete_if do |project|
994
+ project_name_to_test = project['name']
995
+ project_name_to_test.upcase! unless case_sensitive
996
+ no_match = (project_name_to_test != project_name)
997
+ logger.debug { "Comparing: #{project_name_to_test} #{no_match ? '!' : '='}= #{project_name}"}
998
+ return project unless no_match or return_all_matches
999
+ no_match
1000
+ end
1001
+ return nil unless return_all_matches
1002
+ projects
1003
+ end
1004
+
1005
+ def path_resolve(path, path_contains_asset = false, options = { })
1006
+ logger.debug { "Resolving Path: '#{path}' Path Contains Asset: #{path_contains_asset} Options: #{options}" }
1007
+
1008
+ return_first_matching_asset = options.fetch(:return_first_matching_asset, true)
1009
+
1010
+ id_path_ary = [ ]
1011
+ name_path_ary = [ ]
1012
+
1013
+ if path.is_a?(String)
1014
+ # Remove any leading slashes
1015
+ path = path[1..-1] while path.start_with?('/')
1016
+
1017
+ path_ary = path.split('/')
1018
+ elsif path.is_a?(Array)
1019
+ path_ary = path.dup
1020
+ else
1021
+ raise ArgumentError, "path is required to be a String or an Array. Path Class Name: #{path.class.name}"
1022
+ end
1023
+
1024
+ asset_name = path_ary.pop if path_contains_asset
1025
+
1026
+ # The first element must be the name of the project
1027
+ project_name = path_ary.shift
1028
+ raise ArgumentError, 'path must contain a project name.' unless project_name
1029
+ logger.debug { "Search for Project Name: #{project_name}" }
1030
+ project = project_get_by_name(project_name, :return_first_match => true, :projects => options[:projects])
1031
+ return {
1032
+ :name_path => '/',
1033
+ :name_path_ary => [ ],
1034
+
1035
+ :id_path => '/',
1036
+ :id_path_ary => [ ],
1037
+
1038
+ :project => nil,
1039
+ :asset => nil,
1040
+ :folders => [ ]
1041
+ } if !project or project.empty?
1042
+
1043
+ project_id = project['id']
1044
+ id_path_ary << project_id
1045
+ name_path_ary << project_name
1046
+
1047
+ parsed_folders = (project && project['folderCount'] > 0) ? resolve_folder_path(project_id, path_ary) : nil
1048
+ if parsed_folders.nil?
1049
+ asset_folder_id = 0
1050
+ folders = []
1051
+ else
1052
+ id_path_ary.concat(parsed_folders[:id_path_ary])
1053
+ name_path_ary.concat(parsed_folders[:name_path_ary])
1054
+ asset_folder_id = parsed_folders[:id_path_ary].last if path_contains_asset
1055
+ folders = parsed_folders.fetch(:folder_ary, [])
1056
+ end
1057
+
1058
+ asset = nil
1059
+ if path_contains_asset and (asset_folder_id or path_ary.length == 2)
1060
+ # The name of the attribute to search the asset name for (Valid options are :title or :filename)
1061
+ asset_name_field = options[:asset_search_field_name] || :filename
1062
+ case asset_name_field.to_s.downcase.to_sym
1063
+ when :filename, 'filename'
1064
+ asset = asset_get_by_filename(asset_name, project_id, asset_folder_id, :return_first_match => return_first_matching_asset)
1065
+ when :title, 'title'
1066
+ asset = asset_get_by_title(asset_name, project_id, asset_folder_id, :return_first_match => return_first_matching_asset)
1067
+ else
1068
+ raise ArgumentError, ":asset_name_field value is not a valid option. It must be :title or :filename. Current value: #{asset_name_field}"
1069
+ end
1070
+
1071
+ if asset
1072
+ if asset.empty?
1073
+
1074
+
1075
+ elsif asset.is_a?(Array)
1076
+ # Just add the whole array to the array
1077
+ id_path_ary << asset.map { |_asset| _asset['id'] }
1078
+ name_path_ary << asset.map { |_asset| _asset['fileName'] }
1079
+ else
1080
+ id_path_ary << asset['id']
1081
+ name_path_ary << asset['fileName']
1082
+ end
1083
+ end
1084
+ end
1085
+
1086
+ return {
1087
+ :name_path => "/#{name_path_ary.join('/')}",
1088
+ :name_path_ary => name_path_ary,
1089
+
1090
+ :id_path => "/#{id_path_ary.join('/')}",
1091
+ :id_path_ary => id_path_ary,
1092
+
1093
+ :project => project,
1094
+ :asset => asset,
1095
+ :folders => folders
1096
+ }
1097
+ end
1098
+ alias :resolve_path :path_resolve
1099
+
1100
+ # Takes a file system type path and resolves the MediaSilo id's for each of the folders of that path
1101
+ #
1102
+ # @param [Integer] project_id The id of the project the folder resides in
1103
+ # @param [String] path A directory path separated by / of folders to traverse
1104
+ # @param [Integer] parent_id The ID of the parent folder to begin the search in
1105
+ def resolve_folder_path(project_id, path, parent_id = nil)
1106
+ if path.is_a?(Array)
1107
+ path_ary = path.dup
1108
+ elsif path.is_a? String
1109
+ path = path[1..-1] while path.start_with?('/')
1110
+ path_ary = path.split('/')
1111
+ end
1112
+
1113
+ return nil if !path_ary or path_ary.empty?
1114
+
1115
+ id_path_ary = [ ]
1116
+ name_path_ary = [ ]
1117
+
1118
+ folder_name = path_ary.shift
1119
+ name_path_ary << folder_name
1120
+
1121
+ folder = folder_get_by_name(project_id, folder_name, parent_id, :return_first_match => true)
1122
+ return nil unless folder
1123
+
1124
+ folder_ary = [ folder ]
1125
+
1126
+ folder_id = folder['id']
1127
+
1128
+ id_path_ary << folder_id.to_s
1129
+
1130
+ resolved_folder_path = (folder and folder['folderCount'] > 0) ? resolve_folder_path(project_id, path_ary, folder_id) : nil
1131
+
1132
+ unless resolved_folder_path.nil?
1133
+ id_path_ary.concat(resolved_folder_path[:id_path_ary] || [ ])
1134
+ name_path_ary.concat(resolved_folder_path[:name_path_ary] || [ ])
1135
+ folder_ary.concat(resolved_folder_path[:folder_ary] || [ ])
1136
+ end
1137
+
1138
+ return {
1139
+ :id_path_ary => id_path_ary,
1140
+ :name_path_ary => name_path_ary,
1141
+ :folder_ary => folder_ary
1142
+ }
1143
+ end
1144
+
1145
+ end