sony_ci_api 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 606b8f73ec065decebb07d5b157a734f66254d39
4
+ data.tar.gz: 689a7849752c7a72dd26b26fb483ab8ad9e33d99
5
+ SHA512:
6
+ metadata.gz: 6ccc8c7a290d7cbbc4a1a1540278c57c2ca8350dda7faeccb8ccd6064100ccac6a6ea25fd68b733afee4a28a612456bc0becaa94e3e4e6b12288ab1163c4bbf2
7
+ data.tar.gz: 78f9c3cc945ad04b82c9acf63925815b8ada6613602e47f750558008277d96bc16aed16790790ebbd0c61eddbf0bfa16fd0e65cc411ef97999e34caeeb9d87a1
data/bin/sony_ci_api ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/sony_ci_api/sony_ci_admin'
4
+
5
+ args = begin
6
+ Hash[ARGV.slice_before { |a| a.match(/^--/) }.to_a.map { |a| [a[0].gsub(/^--/, ''), a[1..-1]] }]
7
+ rescue
8
+ {}
9
+ end
10
+
11
+ ci = SonyCiAdmin.new(
12
+ # verbose: true,
13
+ credentials_path: 'config/ci.yml')
14
+
15
+ begin
16
+ case args.keys.sort
17
+
18
+ when %w(log up)
19
+ fail ArgumentError.new if args['log'].empty? || args['up'].empty?
20
+ args['up'].each { |path| ci.upload(path, args['log'].first) }
21
+
22
+ when ['down']
23
+ fail ArgumentError.new if args['down'].empty?
24
+ args['down'].each { |id| puts ci.download(id) }
25
+
26
+ when ['list']
27
+ fail ArgumentError.new unless args['list'].empty?
28
+ ci.each { |asset| puts "#{asset['name']}\t#{asset['id']}" }
29
+
30
+ when ['recheck']
31
+ fail ArgumentError.new if args['recheck'].empty?
32
+ args['recheck'].each do |file|
33
+ File.foreach(file) do |line|
34
+ line.chomp!
35
+ id = line.split("\t")[2]
36
+ detail = ci.detail(id).to_s.gsub("\n", ' ')
37
+ puts line + "\t" + detail
38
+ end
39
+ end
40
+
41
+ else
42
+ fail ArgumentError.new
43
+ end
44
+ rescue ArgumentError
45
+ abort 'Usage: --up GLOB --log LOG_FILE | --down ID | --list | --recheck LOG_FILE'
46
+ end
@@ -0,0 +1,187 @@
1
+ require 'yaml'
2
+ require 'curb'
3
+ require 'json'
4
+ require_relative 'sony_ci_basic'
5
+
6
+ class SonyCiAdmin < SonyCiBasic
7
+ include Enumerable
8
+
9
+ # Upload a document to Ci. Underlying API treats large and small files
10
+ # differently, but this should treat both alike.
11
+ def upload(file_path, log_file)
12
+ Uploader.new(self, file_path, log_file).upload
13
+ end
14
+
15
+ # Just the names of items in the workspace. This may include directories.
16
+ def list_names
17
+ list.map { |item| item['name'] } - ['Workspace']
18
+ # A self reference is present even in an empty workspace.
19
+ end
20
+
21
+ # Full metadata for a windowed set of items.
22
+ def list(limit = 50, offset = 0)
23
+ Lister.new(self).list(limit, offset)
24
+ end
25
+
26
+ # Iterate over all items.
27
+ def each
28
+ Lister.new(self).each { |asset| yield asset }
29
+ end
30
+
31
+ # Delete items by asset ID.
32
+ def delete(asset_id)
33
+ Deleter.new(self).delete(asset_id)
34
+ end
35
+
36
+ # Get detailed metadata by asset ID.
37
+ def detail(asset_id)
38
+ Detailer.new(self).detail(asset_id)
39
+ end
40
+
41
+ def multi_details(asset_ids, fields)
42
+ Detailer.new(self).multi_details(asset_ids, fields)
43
+ end
44
+
45
+ class Detailer < SonyCiClient #:nodoc:
46
+ def initialize(ci)
47
+ @ci = ci
48
+ end
49
+
50
+ def detail(asset_id)
51
+ curl = Curl::Easy.http_get('https:'"//api.cimediacloud.com/assets/#{asset_id}") do |c|
52
+ add_headers(c)
53
+ end
54
+ handle_errors(curl)
55
+ JSON.parse(curl.body_str)
56
+ end
57
+
58
+ def multi_details(asset_ids, fields)
59
+ curl = Curl::Easy.http_post('https:''//api.cimediacloud.com/assets/details/bulk',
60
+ JSON.generate('assetIds' => asset_ids,
61
+ 'fields' => fields)
62
+ ) do |c|
63
+ add_headers(c, 'application/json')
64
+ end
65
+ handle_errors(curl)
66
+ JSON.parse(curl.body_str)
67
+ end
68
+ end
69
+
70
+ class Deleter < SonyCiClient #:nodoc:
71
+ def initialize(ci)
72
+ @ci = ci
73
+ end
74
+
75
+ def delete(asset_id)
76
+ curl = Curl::Easy.http_delete('https:'"//api.cimediacloud.com/assets/#{asset_id}") do |c|
77
+ add_headers(c)
78
+ end
79
+ handle_errors(curl)
80
+ end
81
+ end
82
+
83
+ class Lister < SonyCiClient #:nodoc:
84
+ include Enumerable
85
+
86
+ def initialize(ci)
87
+ @ci = ci
88
+ end
89
+
90
+ def list(limit, offset)
91
+ curl = Curl::Easy.http_get('https:''//api.cimediacloud.com/workspaces/' \
92
+ "#{@ci.workspace_id}/contents?limit=#{limit}&offset=#{offset}") do |c|
93
+ add_headers(c)
94
+ end
95
+ handle_errors(curl)
96
+ JSON.parse(curl.body_str)['items']
97
+ end
98
+
99
+ def each
100
+ limit = 5 # Small chunks so it's easy to spot windowing problems
101
+ offset = 0
102
+ loop do
103
+ assets = list(limit, offset)
104
+ break if assets.empty?
105
+ assets.each { |asset| yield asset }
106
+ offset += limit
107
+ end
108
+ end
109
+ end
110
+
111
+ class Uploader < SonyCiClient #:nodoc:
112
+
113
+ # Chunk size 10 for multipart upload.
114
+ CHUNK_SIZE = 10 * 1024 * 1024
115
+
116
+ def initialize(ci, path, log_path)
117
+ @ci = ci
118
+ @path = path
119
+ @log_file = File.open(log_path, 'a')
120
+ end
121
+
122
+ def upload
123
+ file = File.new(@path)
124
+ if file.size >= CHUNK_SIZE
125
+ initiate_multipart_upload(file)
126
+ part = 0
127
+ part = do_multipart_upload_part(file, part) while part
128
+ complete_multipart_upload
129
+ else
130
+ singlepart_upload(file)
131
+ end
132
+
133
+ row = [Time.now, File.basename(@path), @asset_id,
134
+ @ci.detail(@asset_id).to_s.gsub("\n", ' ')]
135
+ @log_file.write(row.join("\t") + "\n")
136
+ @log_file.flush
137
+
138
+ @asset_id
139
+ end
140
+
141
+ private
142
+
143
+ SINGLEPART_URI = 'https://io.cimediacloud.com/upload'
144
+ MULTIPART_URI = 'https://io.cimediacloud.com/upload/multipart'
145
+
146
+ def singlepart_upload(file)
147
+ params = [
148
+ Curl::PostField.file('filename', file.path, File.basename(file.path)),
149
+ Curl::PostField.content('metadata', JSON.generate('workspaceId' => @ci.workspace_id))
150
+ ]
151
+ curl = Curl::Easy.http_post(SINGLEPART_URI, params) do |c|
152
+ c.multipart_form_post = true
153
+ add_headers(c)
154
+ end
155
+ handle_errors(curl)
156
+ @asset_id = JSON.parse(curl.body_str)['assetId']
157
+ end
158
+
159
+ def initiate_multipart_upload(file)
160
+ params = JSON.generate('name' => File.basename(file),
161
+ 'size' => file.size,
162
+ 'workspaceId' => @ci.workspace_id)
163
+ curl = Curl::Easy.http_post(MULTIPART_URI, params) do |c|
164
+ add_headers(c, 'application/json')
165
+ end
166
+ handle_errors(curl)
167
+ @asset_id = JSON.parse(curl.body_str)['assetId']
168
+ end
169
+
170
+ def do_multipart_upload_part(file, part)
171
+ fragment = file.read(CHUNK_SIZE)
172
+ return unless fragment
173
+ curl = Curl::Easy.http_put("#{MULTIPART_URI}/#{@asset_id}/#{part + 1}", fragment) do |c|
174
+ add_headers(c, 'application/octet-stream')
175
+ end
176
+ handle_errors(curl)
177
+ part + 1
178
+ end
179
+
180
+ def complete_multipart_upload
181
+ curl = Curl::Easy.http_post("#{MULTIPART_URI}/#{@asset_id}/complete") do |c|
182
+ add_headers(c)
183
+ end
184
+ handle_errors(curl)
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,74 @@
1
+ require 'yaml'
2
+ require 'curb'
3
+ require 'json'
4
+ require_relative 'sony_ci_client'
5
+
6
+ class SonyCiBasic
7
+ attr_reader :access_token
8
+ attr_reader :verbose
9
+ attr_reader :workspace_id
10
+
11
+ # Either +credentials_path+ or a +credentials+ object itself must be supplied.
12
+ def initialize(opts = {}) # rubocop:disable PerceivedComplexity, CyclomaticComplexity
13
+ unrecognized_opts = opts.keys - [:verbose, :credentials_path, :credentials]
14
+ fail "Unrecognized options #{unrecognized_opts}" unless unrecognized_opts == []
15
+
16
+ @verbose = opts[:verbose] ? true : false
17
+
18
+ fail 'Credentials specified twice' if opts[:credentials_path] && opts[:credentials]
19
+ fail 'No credentials given' if !opts[:credentials_path] && !opts[:credentials]
20
+ credentials = opts[:credentials] || YAML.load_file(opts[:credentials_path])
21
+
22
+ credentials.keys.sort.tap do |actual|
23
+ expected = %w(username password client_id client_secret workspace_id).sort
24
+ fail "Expected #{expected} in ci credentials, not #{actual}" if actual != expected
25
+ end
26
+
27
+ params = {
28
+ 'grant_type' => 'password',
29
+ 'client_id' => credentials['client_id'],
30
+ 'client_secret' => credentials['client_secret']
31
+ }.map { |k, v| Curl::PostField.content(k, v) }
32
+
33
+ curl = Curl::Easy.http_post('https://api.cimediacloud.com/oauth2/token', *params) do |c|
34
+ c.verbose = @verbose
35
+ c.http_auth_types = :basic
36
+ c.username = credentials['username']
37
+ c.password = credentials['password']
38
+ # c.on_missing { |curl, data| puts "4xx: #{data}" }
39
+ # c.on_failure { |curl, data| puts "5xx: #{data}" }
40
+ end
41
+
42
+ @access_token = JSON.parse(curl.body_str)['access_token']
43
+ fail 'OAuth failed' unless @access_token
44
+
45
+ @workspace_id = credentials['workspace_id']
46
+ end
47
+
48
+ # Generate a temporary download URL for an asset.
49
+ def download(asset_id)
50
+ Downloader.new(self).download(asset_id)
51
+ end
52
+
53
+ class Downloader < SonyCiClient #:nodoc:
54
+ @@cache = {}
55
+
56
+ def initialize(ci)
57
+ @ci = ci
58
+ end
59
+
60
+ def download(asset_id)
61
+ hit = @@cache[asset_id]
62
+ if !hit || hit[:expires] < Time.now
63
+
64
+ curl = Curl::Easy.http_get('https'"://api.cimediacloud.com/assets/#{asset_id}/download") do |c|
65
+ add_headers(c)
66
+ end
67
+ handle_errors(curl)
68
+ url = JSON.parse(curl.body_str)['location']
69
+ @@cache[asset_id] = { url: url, expires: Time.now + 3 * 60 * 60 }
70
+ end
71
+ @@cache[asset_id][:url]
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,15 @@
1
+ class SonyCiClient #:nodoc:
2
+ def add_headers(curl, mime = nil)
3
+ # on_missing and on_failure exist...
4
+ # but any exceptions are caught and turned into warnings:
5
+ # You need to check the response code at the end
6
+ # if you want the execution path to change.
7
+ curl.verbose = @ci.verbose
8
+ curl.headers['Authorization'] = "Bearer #{@ci.access_token}"
9
+ curl.headers['Content-Type'] = mime if mime
10
+ end
11
+
12
+ def handle_errors(curl)
13
+ fail "#{curl.status}: #{curl.url}\nHEADERS: #{curl.headers}\nPOST: #{curl.post_body}\nRESPONSE: #{curl.body}" if curl.response_code.to_s !~ /^2../
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sony_ci_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Chuck McCallum
8
+ - Andrew Myers
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-10-06 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Wrapper for the Sony Ci API (http://developers.cimediacloud.com/)
15
+ email: andrew_myers@wgbh.org
16
+ executables:
17
+ - sony_ci_api
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - bin/sony_ci_api
22
+ - lib/sony_ci_api/sony_ci_admin.rb
23
+ - lib/sony_ci_api/sony_ci_basic.rb
24
+ - lib/sony_ci_api/sony_ci_client.rb
25
+ homepage: https://github.com/WGBH/sony_ci_api
26
+ licenses:
27
+ - MIT
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.5.1
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Sony Ci API
49
+ test_files: []