active_encode 0.1.1 → 0.2

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 (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