active_encode 0.1.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +3 -2
  4. data/Gemfile +35 -0
  5. data/README.md +15 -14
  6. data/Rakefile +5 -10
  7. data/active_encode.gemspec +8 -5
  8. data/app/jobs/active_encode/polling_job.rb +20 -0
  9. data/app/models/active_encode/encode_record.rb +5 -0
  10. data/db/migrate/20180822021048_create_active_encode_encode_records.rb +13 -0
  11. data/lib/active_encode.rb +1 -0
  12. data/lib/active_encode/base.rb +6 -2
  13. data/lib/active_encode/callbacks.rb +18 -35
  14. data/lib/active_encode/core.rb +64 -20
  15. data/lib/active_encode/engine.rb +7 -0
  16. data/lib/active_encode/engine_adapter.rb +2 -2
  17. data/lib/active_encode/engine_adapters/active_job_adapter.rb +7 -3
  18. data/lib/active_encode/engine_adapters/elastic_transcoder_adapter.rb +15 -15
  19. data/lib/active_encode/engine_adapters/inline_adapter.rb +6 -1
  20. data/lib/active_encode/engine_adapters/matterhorn_adapter.rb +18 -18
  21. data/lib/active_encode/engine_adapters/shingoncoder_adapter.rb +13 -9
  22. data/lib/active_encode/engine_adapters/test_adapter.rb +19 -12
  23. data/lib/active_encode/engine_adapters/zencoder_adapter.rb +10 -10
  24. data/lib/active_encode/global_id.rb +16 -0
  25. data/lib/active_encode/input.rb +9 -0
  26. data/lib/active_encode/output.rb +9 -0
  27. data/lib/active_encode/persistence.rb +45 -0
  28. data/lib/active_encode/polling.rb +24 -0
  29. data/lib/active_encode/status.rb +2 -6
  30. data/lib/active_encode/technical_metadata.rb +16 -1
  31. data/lib/active_encode/version.rb +1 -1
  32. data/spec/fixtures/elastic_transcoder/job_canceled.json +1 -1
  33. data/spec/fixtures/elastic_transcoder/job_completed.json +1 -1
  34. data/spec/fixtures/elastic_transcoder/job_created.json +1 -1
  35. data/spec/fixtures/elastic_transcoder/job_failed.json +1 -1
  36. data/spec/fixtures/elastic_transcoder/job_progressing.json +1 -1
  37. data/spec/integration/elastic_transcoder_adapter_spec.rb +87 -167
  38. data/spec/integration/matterhorn_adapter_spec.rb +34 -79
  39. data/spec/integration/shingoncoder_adapter_spec.rb +1 -1
  40. data/spec/integration/zencoder_adapter_spec.rb +1 -1
  41. data/spec/rails_helper.rb +22 -0
  42. data/spec/shared_specs/engine_adapter_specs.rb +124 -0
  43. data/spec/test_app_templates/lib/generators/test_app_generator.rb +15 -0
  44. data/spec/units/callbacks_spec.rb +16 -17
  45. data/spec/units/core_spec.rb +121 -2
  46. data/spec/units/engine_adapter_spec.rb +0 -12
  47. data/spec/units/global_id_spec.rb +49 -0
  48. data/spec/units/input_spec.rb +12 -0
  49. data/spec/units/output_spec.rb +12 -0
  50. data/spec/units/persistence_spec.rb +57 -0
  51. data/spec/units/polling_job_spec.rb +86 -0
  52. data/spec/units/polling_spec.rb +22 -0
  53. data/spec/units/status_spec.rb +21 -2
  54. metadata +89 -20
@@ -0,0 +1,7 @@
1
+ require 'rails'
2
+
3
+ module ActiveEncode
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace ActiveEncode
6
+ end
7
+ end
@@ -10,7 +10,7 @@ module ActiveEncode
10
10
 
11
11
  included do
12
12
  class_attribute :_engine_adapter, instance_accessor: false, instance_predicate: false
13
- self.engine_adapter = :inline
13
+ self.engine_adapter = :test
14
14
  end
15
15
 
16
16
  # Includes the setter method for changing the active engine adapter.
@@ -38,7 +38,7 @@ module ActiveEncode
38
38
  end
39
39
  end
40
40
 
41
- ENGINE_ADAPTER_METHODS = %i[create find list cancel purge remove_output].freeze
41
+ ENGINE_ADAPTER_METHODS = %i[create find cancel].freeze
42
42
 
43
43
  def engine_adapter?(object)
44
44
  ENGINE_ADAPTER_METHODS.all? { |meth| object.respond_to?(meth) }
@@ -1,13 +1,17 @@
1
1
  module ActiveEncode
2
2
  module EngineAdapters
3
3
  class ActiveJobAdapter
4
- def create(_encode) end
4
+ def initialize
5
+ ActiveSupport::Deprecation.warn("The ActiveJobAdapter is deprecated and will be removed in ActiveEncode 0.3.")
6
+ end
5
7
 
6
- def find(_id, _opts = {}) end
8
+ def create(_input_url, _options) end
9
+
10
+ def find(_id) end
7
11
 
8
12
  def list(*_filters) end
9
13
 
10
- def cancel(_encode) end
14
+ def cancel(_id end
11
15
 
12
16
  def purge(_encode) end
13
17
 
@@ -2,20 +2,20 @@ module ActiveEncode
2
2
  module EngineAdapters
3
3
  class ElasticTranscoderAdapter
4
4
  # TODO: add a stub for an input helper (supplied by an initializer) that transforms encode.input into a zencoder accepted url
5
- def create(encode)
5
+ def create(input_url, options = {})
6
6
  job = client.create_job(
7
- input: { key: encode.input },
8
- pipeline_id: encode.options[:pipeline_id],
9
- output_key_prefix: encode.options[:output_key_prefix],
10
- outputs: encode.options[:outputs],
11
- user_metadata: encode.options[:user_metadata]
7
+ input: { key: input_url },
8
+ pipeline_id: options[:pipeline_id],
9
+ output_key_prefix: options[:output_key_prefix],
10
+ outputs: options[:outputs],
11
+ user_metadata: options[:user_metadata]
12
12
  ).job
13
13
 
14
- build_encode(get_job_details(job.id), encode.class)
14
+ build_encode(job)
15
15
  end
16
16
 
17
17
  def find(id, opts = {})
18
- build_encode(get_job_details(id), opts[:cast])
18
+ build_encode(get_job_details(id))
19
19
  end
20
20
 
21
21
  # TODO: implement list_jobs_by_pipeline and list_jobs_by_status
@@ -24,9 +24,9 @@ module ActiveEncode
24
24
  end
25
25
 
26
26
  # Can only cancel jobs with status = "Submitted"
27
- def cancel(encode)
28
- response = client.cancel_job(id: encode.id)
29
- build_encode(get_job_details(encode.id), encode.class) if response.successful?
27
+ def cancel(id)
28
+ response = client.cancel_job(id: id)
29
+ build_encode(get_job_details(id)) if response.successful?
30
30
  end
31
31
 
32
32
  def purge(_encode)
@@ -48,9 +48,9 @@ module ActiveEncode
48
48
  client.read_job(id: job_id).job
49
49
  end
50
50
 
51
- def build_encode(job, cast)
51
+ def build_encode(job)
52
52
  return nil if job.nil?
53
- encode = cast.new(convert_input(job), convert_options(job))
53
+ encode = ActiveEncode::Base.new(convert_input(job), convert_options(job))
54
54
  encode.id = job.id
55
55
  encode.state = convert_state(job)
56
56
  encode.current_operations = convert_current_operations(job)
@@ -66,7 +66,7 @@ module ActiveEncode
66
66
 
67
67
  def convert_time(time_millis)
68
68
  return nil if time_millis.nil?
69
- Time.at(time_millis / 1000).iso8601
69
+ Time.at(time_millis / 1000)
70
70
  end
71
71
 
72
72
  def convert_state(job)
@@ -91,7 +91,7 @@ module ActiveEncode
91
91
  case job.status
92
92
  when "Submitted"
93
93
  10
94
- when "Progressing"
94
+ when "Progressing", "Canceled", "Error"
95
95
  50
96
96
  when "Complete"
97
97
  100
@@ -4,7 +4,12 @@ module ActiveEncode
4
4
  class_attribute :encodes, instance_accessor: false, instance_predicate: false
5
5
  InlineAdapter.encodes ||= {}
6
6
 
7
- def create(encode)
7
+ def initialize
8
+ ActiveSupport::Deprecation.warn("The InlineAdapter is deprecated and will be removed in ActiveEncode 0.3.")
9
+ end
10
+
11
+ def create(input_url, options = {})
12
+ encode = ActiveEncode::Base.new(input_url, options)
8
13
  encode.id = SecureRandom.uuid
9
14
  self.class.encodes[encode.id] = encode
10
15
  # start encode
@@ -5,27 +5,27 @@ module ActiveEncode
5
5
  class MatterhornAdapter
6
6
  DEFAULT_ARGS = { 'flavor' => 'presenter/source' }.freeze
7
7
 
8
- def create(encode)
9
- workflow_id = encode.options[:preset] || "full"
10
- workflow_om = if encode.input.is_a? Hash
11
- create_multiple_files(encode.input, workflow_id)
12
- else
13
- Rubyhorn.client.addMediaPackageWithUrl(DEFAULT_ARGS.merge('workflow' => workflow_id, 'url' => encode.input, 'filename' => File.basename(encode.input), 'title' => File.basename(encode.input)))
14
- end
15
- build_encode(get_workflow(workflow_om), encode.class)
8
+ def create(input_url, options = {})
9
+ workflow_id = options[:preset] || "full"
10
+ # workflow_om = if encode.input.is_a? Hash
11
+ # create_multiple_files(encode.input, workflow_id)
12
+ # else
13
+ workflow_om = Rubyhorn.client.addMediaPackageWithUrl(DEFAULT_ARGS.merge('workflow' => workflow_id, 'url' => input_url, 'filename' => File.basename(input_url), 'title' => File.basename(input_url)))
14
+ # end
15
+ build_encode(get_workflow(workflow_om))
16
16
  end
17
17
 
18
18
  def find(id, opts = {})
19
- build_encode(fetch_workflow(id), opts[:cast])
19
+ build_encode(fetch_workflow(id))
20
20
  end
21
21
 
22
22
  def list(*_filters)
23
23
  raise NotImplementedError # TODO: implement this
24
24
  end
25
25
 
26
- def cancel(encode)
27
- workflow_om = Rubyhorn.client.stop(encode.id)
28
- build_encode(get_workflow(workflow_om), encode.class)
26
+ def cancel(id)
27
+ workflow_om = Rubyhorn.client.stop(id)
28
+ build_encode(get_workflow(workflow_om))
29
29
  end
30
30
 
31
31
  def purge(encode)
@@ -41,7 +41,7 @@ module ActiveEncode
41
41
  end
42
42
  purged_workflow = purge_outputs(get_workflow(workflow_om))
43
43
  # Rubyhorn.client.delete_instance(encode.id) #Delete is not working so workflow instances can always be retrieved later!
44
- build_encode(purged_workflow, encode.class)
44
+ build_encode(purged_workflow)
45
45
  end
46
46
 
47
47
  def remove_output(encode, output_id)
@@ -79,9 +79,9 @@ module ActiveEncode
79
79
  end
80
80
  end
81
81
 
82
- def build_encode(workflow, cast)
82
+ def build_encode(workflow)
83
83
  return nil if workflow.nil?
84
- encode = cast.new(convert_input(workflow), convert_options(workflow))
84
+ encode = ActiveEncode::Base.new(convert_input(workflow), convert_options(workflow))
85
85
  encode.id = convert_id(workflow)
86
86
  encode.state = convert_state(workflow)
87
87
  encode.current_operations = convert_current_operations(workflow)
@@ -146,17 +146,17 @@ module ActiveEncode
146
146
 
147
147
  def convert_created_at(workflow)
148
148
  created_at = workflow.xpath('mediapackage/@start').last.to_s
149
- created_at.present? ? Time.parse(created_at).iso8601 : nil
149
+ created_at.present? ? Time.parse(created_at) : nil
150
150
  end
151
151
 
152
152
  def convert_updated_at(workflow)
153
153
  updated_at = workflow.xpath('//operation[@state!="INSTANTIATED"]/completed/text()').last.to_s
154
- updated_at.present? ? Time.strptime(updated_at, "%Q").utc.iso8601 : nil
154
+ updated_at.present? ? Time.strptime(updated_at, "%Q") : nil
155
155
  end
156
156
 
157
157
  def convert_finished_at(workflow)
158
158
  finished_at = workflow.xpath('//operation[@state!="INSTANTIATED"]/completed/text()').last.to_s
159
- finished_at.present? ? Time.strptime(finished_at, "%Q").utc.iso8601 : nil
159
+ finished_at.present? ? Time.strptime(finished_at, "%Q") : nil
160
160
  end
161
161
 
162
162
  def convert_options(workflow)
@@ -4,23 +4,27 @@ require 'active_support/core_ext'
4
4
  module ActiveEncode
5
5
  module EngineAdapters
6
6
  class ShingoncoderAdapter < ZencoderAdapter
7
+ def initialize
8
+ ActiveSupport::Deprecation.warn("The ShingoncoderAdapter is deprecated and will be removed in ActiveEncode 0.3.")
9
+ end
10
+
7
11
  # @param [ActiveEncode::Base] encode
8
- def create(encode)
9
- response = Shingoncoder::Job.create(input: encode.input)
10
- build_encode(job_details(response.body["id"]), encode.class)
12
+ def create(input_url, options = {})
13
+ response = Shingoncoder::Job.create(input: input_url)
14
+ build_encode(job_details(response.body["id"]))
11
15
  end
12
16
 
13
17
  # @param [Fixnum] id
14
18
  # @param [Hash] opts
15
19
  # @option opts :cast the class to cast the encoding job to.
16
20
  def find(id, opts = {})
17
- build_encode(job_details(id), opts[:cast])
21
+ build_encode(job_details(id))
18
22
  end
19
23
 
20
24
  # @param [ActiveEncode::Base] encode
21
- def cancel(encode)
22
- response = Shingoncoder::Job.cancel(encode.id)
23
- build_encode(job_details(encode.id), encode.class) if response.success?
25
+ def cancel(id)
26
+ response = Shingoncoder::Job.cancel(id)
27
+ build_encode(job_details(id)) if response.success?
24
28
  end
25
29
 
26
30
  private
@@ -38,9 +42,9 @@ module ActiveEncode
38
42
 
39
43
  # @param [Shingoncoder::Response] job_details
40
44
  # @param [Class] cast the class of object to instantiate and return
41
- def build_encode(job_details, cast)
45
+ def build_encode(job_details)
42
46
  return nil if job_details.nil?
43
- encode = cast.new(convert_input(job_details), convert_options(job_details))
47
+ encode = ActiveEncode::Base.new(convert_input(job_details), convert_options(job_details))
44
48
  encode.id = job_details.body["job"]["id"].to_s
45
49
  encode.state = convert_state(job_details)
46
50
  progress = job_progress(encode.id)
@@ -5,25 +5,32 @@ module ActiveEncode
5
5
  @encodes = {}
6
6
  end
7
7
 
8
- def create(encode)
9
- encode.id = SecureRandom.uuid
10
- @encodes[encode.id] = encode
11
- encode.state = :running
12
- encode
8
+ def create(input_url, options = {})
9
+ new_encode = ActiveEncode::Base.new(input_url, options)
10
+ new_encode.id = SecureRandom.uuid
11
+ new_encode.state = :running
12
+ new_encode.created_at = Time.now
13
+ new_encode.updated_at = Time.now
14
+ @encodes[new_encode.id] = new_encode
15
+ new_encode
13
16
  end
14
17
 
15
18
  def find(id, _opts = {})
16
- @encodes[id]
19
+ new_encode = @encodes[id]
20
+ # Update the updated_at time to simulate changes
21
+ new_encode.updated_at = Time.now
22
+ new_encode
17
23
  end
18
24
 
19
- def list(*_filters)
20
- raise NotImplementedError
25
+ def cancel(id)
26
+ new_encode = @encodes[id]
27
+ new_encode.state = :cancelled
28
+ new_encode.updated_at = Time.now
29
+ new_encode
21
30
  end
22
31
 
23
- def cancel(encode)
24
- e = @encodes[encode.id]
25
- e.state = :cancelled
26
- e
32
+ def list(*_filters)
33
+ raise NotImplementedError
27
34
  end
28
35
 
29
36
  def purge(encode)
@@ -1,23 +1,23 @@
1
1
  module ActiveEncode
2
2
  module EngineAdapters
3
3
  class ZencoderAdapter
4
- # TODO: add a stub for an input helper (supplied by an initializer) that transforms encode.input into a zencoder accepted url
5
- def create(encode)
6
- response = Zencoder::Job.create(input: encode.input.to_s)
7
- build_encode(get_job_details(response.body["id"]), encode.class)
4
+ # TODO: add a stub for an input helper (supplied by an initializer) that transforms encode.input.url into a zencoder accepted url
5
+ def create(input_url, options = {})
6
+ response = Zencoder::Job.create(input: input_url.to_s)
7
+ build_encode(get_job_details(response.body["id"]))
8
8
  end
9
9
 
10
10
  def find(id, opts = {})
11
- build_encode(get_job_details(id), opts[:cast])
11
+ build_encode(get_job_details(id))
12
12
  end
13
13
 
14
14
  def list(*_filters)
15
15
  raise NotImplementedError
16
16
  end
17
17
 
18
- def cancel(encode)
19
- response = Zencoder::Job.cancel(encode.id)
20
- build_encode(get_job_details(encode.id), encode.class) if response.success?
18
+ def cancel(id)
19
+ response = Zencoder::Job.cancel(id)
20
+ build_encode(get_job_details(id)) if response.success?
21
21
  end
22
22
 
23
23
  def purge(_encode)
@@ -38,9 +38,9 @@ module ActiveEncode
38
38
  Zencoder::Job.progress(job_id)
39
39
  end
40
40
 
41
- def build_encode(job_details, cast)
41
+ def build_encode(job_details)
42
42
  return nil if job_details.nil?
43
- encode = cast.new(convert_input(job_details), convert_options(job_details))
43
+ encode = ActiveEncode::Base.new(convert_input(job_details), convert_options(job_details))
44
44
  encode.id = job_details.body["job"]["id"].to_s
45
45
  encode.state = convert_state(job_details)
46
46
  job_progress = get_job_progress(encode.id)
@@ -0,0 +1,16 @@
1
+ require 'globalid'
2
+
3
+ module ActiveEncode
4
+ module GlobalID
5
+ extend ActiveSupport::Concern
6
+ include ::GlobalID::Identification
7
+
8
+ def ==(other)
9
+ other.is_a?(ActiveEncode::Base) && to_global_id == other.to_global_id
10
+ end
11
+
12
+ def to_global_id(options = {})
13
+ super(app: 'ActiveEncode')
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveEncode
2
+ class Input
3
+ include Status
4
+ include TechnicalMetadata
5
+
6
+ attr_accessor :id
7
+ attr_accessor :url
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveEncode
2
+ class Output
3
+ include Status
4
+ include TechnicalMetadata
5
+
6
+ attr_accessor :id
7
+ attr_accessor :url
8
+ end
9
+ end
@@ -0,0 +1,45 @@
1
+ require 'active_support'
2
+
3
+ module ActiveEncode
4
+ module Persistence
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ after_find do |encode|
9
+ persist(persistence_model_attributes(encode))
10
+ end
11
+
12
+ after_create do |encode|
13
+ persist(persistence_model_attributes(encode))
14
+ end
15
+
16
+ after_cancel do |encode|
17
+ persist(persistence_model_attributes(encode))
18
+ end
19
+
20
+ after_reload do |encode|
21
+ persist(persistence_model_attributes(encode))
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def persist(encode_attributes)
28
+ model = ActiveEncode::EncodeRecord.find_or_initialize_by(global_id: encode_attributes[:global_id])
29
+ model.update(encode_attributes) # Don't fail if persisting doesn't succeed?
30
+ end
31
+
32
+ def persistence_model_attributes(encode)
33
+ {
34
+ global_id: encode.to_global_id.to_s,
35
+ state: encode.state,
36
+ adapter: encode.class.engine_adapter.class.name,
37
+ title: encode.input.url.to_s,
38
+ # FIXME: Need to ensure that these values come through or else validations will fail
39
+ created_at: encode.created_at,
40
+ updated_at: encode.updated_at,
41
+ raw_object: encode.to_json
42
+ }
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ require 'active_support'
2
+ require 'active_model/callbacks'
3
+
4
+ module ActiveEncode
5
+ module Polling
6
+ extend ActiveSupport::Concern
7
+
8
+ POLLING_WAIT_TIME = 10.seconds.freeze
9
+
10
+ CALLBACKS = [
11
+ :after_status_update, :after_error, :after_cancelled, :after_complete
12
+ ].freeze
13
+
14
+ included do
15
+ extend ActiveModel::Callbacks
16
+
17
+ define_model_callbacks :status_update, :error, :cancelled, :complete, only: :after
18
+
19
+ after_create do |encode|
20
+ ActiveEncode::PollingJob.set(wait: POLLING_WAIT_TIME).perform_later(encode)
21
+ end
22
+ end
23
+ end
24
+ end