diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a0a51ee8..9fdfddef 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 1000` -# on 2024-08-29 18:05:38 UTC using RuboCop version 1.65.1. +# on 2024-09-03 17:09:41 UTC using RuboCop version 1.65.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -97,10 +97,11 @@ RSpec/ExampleLength: Exclude: - 'spec/models/video_blob_spec.rb' -# Offense count: 1 +# Offense count: 2 # Configuration parameters: Max. RSpec/MultipleExpectations: Exclude: + - 'spec/listeners/disk_listener_spec.rb' - 'spec/requests/movies_request_spec.rb' # Offense count: 1 @@ -116,6 +117,11 @@ RSpec/NamedSubject: Exclude: - 'spec/models/video_blob_spec.rb' +# Offense count: 1 +RSpec/SubjectStub: + Exclude: + - 'spec/listeners/disk_listener_spec.rb' + # Offense count: 12 Rails/I18nLocaleTexts: Exclude: diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index c66c0871..775240d5 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -20,6 +20,7 @@ @import "checkbox_toggle"; @import "bg_process"; @import "autocomplete"; +@import "movie"; // @import "./custom"; // app/javascript/packs/stylesheets/_custom.scss @@ -33,6 +34,15 @@ body { body { font-family: "Lucida Console", Monaco, monospace; + + main { + padding: 1em; + padding-bottom: 20em; + } +} + +.nav-link { + padding: 0.5em; } .height-100 { @@ -47,10 +57,8 @@ body { height: 60px; } -.sticky-element { - position: -webkit-sticky; - position: sticky; - top: 0; +.margin-auto { + margin: auto; } // Console for basic shell output @@ -76,6 +84,31 @@ body { padding: 0.5rem; } +.certification-rating { + align-content: center; + align-items: center; + border-radius: 2px; + border: 1px solid rgba(255, 255, 255, 0.6); + line-height: 1; + margin-right: 7px; + padding: .06em 4px .06em; + white-space: nowrap; +} + +.cast { + display: flex; + overflow-x: scroll; + + .cast-member { + .img-poster { + padding-bottom: 1em; + } + + width: 200px; + } +} + + .h-90 { height: 90%; } @@ -96,6 +129,10 @@ body { display: inline-grid; } +.hidden { + display: none; +} + .overflow-scroll-y { overflow-y: scroll; } @@ -115,6 +152,7 @@ body { .img-poster { height: 300px; width: 200px; + display: inline-block; } .width-225 { diff --git a/app/assets/stylesheets/bg_process.scss b/app/assets/stylesheets/bg_process.scss index f9c402eb..044b3c31 100644 --- a/app/assets/stylesheets/bg_process.scss +++ b/app/assets/stylesheets/bg_process.scss @@ -1,3 +1,11 @@ +#bg-processes.fixed { + max-width: 23%; + position: fixed; + right: 1em; + bottom: 5em; + z-index: 1000; +} + .bg-process { --bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85); --bs-toast-border-color: var(--bs-border-color-translucent); @@ -48,7 +56,6 @@ } - .job { --bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85); --bs-toast-border-color: var(--bs-border-color-translucent); @@ -97,3 +104,18 @@ } } + +#bg-processes.compact { + .bg-process { + margin: 0px; + border-radius: 0; + border: 0.2em solid var(--bs-toast-border-color); + + .header { + display: none; + padding: 0.5em; + } + + } + +} diff --git a/app/assets/stylesheets/minimal.scss b/app/assets/stylesheets/minimal.scss index 9961ce7b..dbdf1fa7 100644 --- a/app/assets/stylesheets/minimal.scss +++ b/app/assets/stylesheets/minimal.scss @@ -33,34 +33,34 @@ } input { - border-radius: 2px 2px 0px 0px; background-color: transparent; + border-color: transparent; + border-radius: 2px 2px 0px 0px; border: 0; color: $white; display: block; font-family: "Lucida Console", Monaco, monospace; font-size: 20px; height: 35px; - padding-bottom: 3px; line-height: 1.2; outline: none; + padding-bottom: 3px; width: 100%; - border-color: transparent; } label { display: block; + font-size: 22px; height: 100%; left: 0; + overflow: hidden; pointer-events: none; position: absolute; - width: 100%; - font-size: 22px; + text-overflow: ellipsis; top: 0px; transition: all .4s; - overflow: hidden; - text-overflow: ellipsis; white-space: nowrap; + width: 100%; } input:focus + label, input.has-val + label { diff --git a/app/assets/stylesheets/movie.scss b/app/assets/stylesheets/movie.scss new file mode 100644 index 00000000..cf816cc0 --- /dev/null +++ b/app/assets/stylesheets/movie.scss @@ -0,0 +1,3 @@ +.movie-facts { + color: $light-gray +} diff --git a/app/components/cast_component.html.erb b/app/components/cast_component.html.erb new file mode 100644 index 00000000..50622fb0 --- /dev/null +++ b/app/components/cast_component.html.erb @@ -0,0 +1,11 @@ +
+ <% cast.each do |cast| %> +
+ <%= imdb_image_tag cast[:profile_path], width: 150, klass: 'img-poster' %> +
+
<%= cast[:name] %>
+

<%= cast[:character] %>

+
+
+ <% end %> +
diff --git a/app/components/cast_component.rb b/app/components/cast_component.rb new file mode 100644 index 00000000..bc937f85 --- /dev/null +++ b/app/components/cast_component.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class CastComponent < ViewComponent::Base + include ImdbHelper + Member = Types::Hash.schema( + character: Types::String, + name: Types::String, + profile_path: Types::String.optional + ).with_key_transform(&:to_sym) + Cast = Types::Coercible::Array.of(Member) + + extend Dry::Initializer + option :video, Types.Instance(::Video) + + strip_trailing_whitespace + + def cast + return [] if video.credits.nil? + + @cast ||= Cast[video.credits['cast']] + end + + def render? + cast.any? + end +end diff --git a/app/components/load_disk_process_component.html.erb b/app/components/load_disk_process_component.html.erb index 0efb4fa6..82e29209 100644 --- a/app/components/load_disk_process_component.html.erb +++ b/app/components/load_disk_process_component.html.erb @@ -1,25 +1,29 @@ -<%= render ProcessComponent.new dom_id: dom_id do |c| %> - <%= c.with_title do %> - Disk Status - <% end %> - <%= c.with_body do %> - <% if job_active? && disks_loading.any? %> - <% disks_loading.each do |disk| %> -

- Loading <%= link_to disk.name, the_movie_dbs_path(search: { query: disk.name.titleize }) %> ... <%= link_to 'View Details', job_path(job) %> -

-

<%= Array.wrap(job.metadata['message']).last %>

- <% end %> - <% elsif disks_not_ejected.any? %> - <% disks_not_ejected.each do |disk| %> -

- <%= link_to disk.name, the_movie_dbs_path(search: { query: disk.name.titleize }) %> is ready to be ripped. <%= link_to 'eject', eject_disk_path(disk), data: { turbo_method: :post } %> -

+<% if hidden? %> + +<% else %> + <%= render ProcessComponent.new dom_id: dom_id do |c| %> + <%= c.with_title do %> + Disk Status + <% end %> + <%= c.with_body do %> + <% if job_active? && disks_loading.any? %> + <% disks_loading.each do |disk| %> +

+ Loading <%= link_to disk.name, the_movie_dbs_path(search: { query: disk.name.titleize }) %> ... <%= link_to 'View Details', job_path(job) %> +

+

<%= Array.wrap(job.metadata['message']).last %>

+ <% end %> + <% elsif disks_not_ejected.any? %> + <% disks_not_ejected.each do |disk| %> +

+ <%= link_to disk.name, the_movie_dbs_path(search: { query: disk.name.titleize }) %> is ready to be ripped. <%= link_to 'eject', eject_disk_path(disk), data: { turbo_method: :post } %> +

+ <% end %> + <% elsif job_active? %> +

Loading disk info ...<%= link_to 'View Details', job_path(job) %>

+ <% else %> + No disks found - insert disk to continue <% end %> - <% elsif job_active? %> -

Loading disk info ...<%= link_to 'View Details', job_path(job) %>

- <% else %> - No disks found - insert disk to continue <% end %> <% end %> <% end %> diff --git a/app/components/load_disk_process_component.rb b/app/components/load_disk_process_component.rb index 5a0ccc07..2021d5ca 100644 --- a/app/components/load_disk_process_component.rb +++ b/app/components/load_disk_process_component.rb @@ -7,6 +7,14 @@ def self.job Job.sort_by_created_at.active.find_by(name: 'LoadDiskWorker') end + def self.show? + job&.active? + end + + def hidden? + !self.class.show? && disks_not_ejected.empty? + end + def job_active? job&.active? end diff --git a/app/components/movie_title_table_component.html.erb b/app/components/movie_title_table_component.html.erb index d518b1dd..564bee95 100644 --- a/app/components/movie_title_table_component.html.erb +++ b/app/components/movie_title_table_component.html.erb @@ -1,6 +1,5 @@ <% if disks.any? %> <%= simple_form_for :movies, url: rip_movie_path(movie) do |f| %> - <% disk = disks.first %> <%= hidden_field_tag :disk_id, disk&.id %> @@ -15,16 +14,15 @@ - <% disk.disk_titles.sort_by { |d| movie.runtime_range.include?(d.duration) ? 0 : 1 }.each do |disk_title| %> + <% disk_titles_with_info.each do |info| %> + <% disk_title = info.disk_title %> <%= hidden_field_tag "movies[][disk_title_id]", disk_title.id %> - <% in_range = movie.runtime_range.include?(disk_title.duration) %> - <% text_class = 'text-primary-emphasis' if in_range %> - <% text_class = 'text-success-emphasis' if movie.video_blobs.any? && in_range %> + <% text_class = 'text-primary-emphasis' if info.within_range %> - + <% end %> @@ -74,8 +65,12 @@
<%= disk_title.id %>

- <% if movie.ripped_disk_titles.any? { _1.name == disk_title.name } %> + <% if info.ripped? %> <%= icon('square-check') %> <% else %> <%= icon('square') %> @@ -33,7 +31,7 @@

- <% if movie.ripped_disk_titles.find { _1.name == disk_title.name }&.video_blob&.uploaded? %> + <% if info.uploaded? %> <%= icon('square-check') %> <% else %> <%= icon('square') %> @@ -47,7 +45,9 @@ (<%= disk_title.video&.title %>) <% end %>

<%= distance_of_time_in_words(disk_title.duration.seconds) %> + <%= distance_of_time_in_words(disk_title.duration.seconds) %> + <%= number_to_human_size(disk_title.size, precision: 3) %> <% if disk_title.size >= free_disk_space %> @@ -55,16 +55,7 @@ <% end %> - <% if movie.ripped_disk_titles.find { _1.name == disk_title.name }&.video_blob&.uploaded? %> - <%= select_tag "movies[][extra_type]", options_for_select(VideoBlob.extra_types.keys, nil), prompt: "Don't Rip" %> - <% elsif in_range && feature_film_selected? %> - <%= select_tag "movies[][extra_type]", options_for_select(VideoBlob.extra_types.keys, 'shorts'), prompt: "Don't Rip" %> - <% elsif in_range %> - <% feature_film_selected! %> - <%= select_tag "movies[][extra_type]", options_for_select(VideoBlob.extra_types.keys, 'feature_films'), prompt: "Don't Rip" %> - <% else %> - <%= select_tag "movies[][extra_type]", options_for_select(VideoBlob.extra_types.keys, 'other'), prompt: "Don't Rip" %> - <% end %> + <%= select_tag "movies[][extra_type]", options_for_select(VideoBlob.extra_types.keys, info.extra_type), prompt: "Don't Rip" %>
<% end %> -<% elsif job&.active? %> -

Stand by the disk is still being loading from the CD drive this could take a while. Page will update once the disk is ready so no need to refresh the page but if you do it won't hurt anything.

<% else %> -

Sorry no Disk is currently loaded or could be detected. Refresh page if your seeing this warning but know a disk is present.

+
+ <% if movie.auto_start %> +

Will Start ripping movie as soon as the disk is ready.

+ <% else %> + <%= link_to 'Auto Start', auto_start_movie_path(movie), class: 'btn btn-primary', data: { turbo_method: :post } %> + <% end %> +
<% end %> diff --git a/app/components/movie_title_table_component.rb b/app/components/movie_title_table_component.rb index 01cd2cb9..6067c252 100644 --- a/app/components/movie_title_table_component.rb +++ b/app/components/movie_title_table_component.rb @@ -11,6 +11,18 @@ def dom_id "#{self.class.name.parameterize}-#{movie.id}" end + def disks_loading + @disks_loading ||= Disk.loading + end + + def disk_titles_with_info + MovieDiskTitleSelectorService.call(movie:, disk:) + end + + def disk + @disk ||= disks.first + end + def free_disk_space @free_disk_space ||= stats.block_size * stats.blocks_available end diff --git a/app/components/process_component.html.erb b/app/components/process_component.html.erb index 5e8a183b..112bdd6d 100644 --- a/app/components/process_component.html.erb +++ b/app/components/process_component.html.erb @@ -1,9 +1,9 @@
<%= title %>
<% if link %> -
<%= body %>
- +
<%= body %>
+ <% else %> -
<%= body %>
+
<%= body %>
<% end %>
diff --git a/app/components/rip_process_component.html.erb b/app/components/rip_process_component.html.erb index 1f6515a1..9adb7897 100644 --- a/app/components/rip_process_component.html.erb +++ b/app/components/rip_process_component.html.erb @@ -1,4 +1,4 @@ -<% if hide %> +<% if hidden? %> <% else %> <%= render ProcessComponent.new dom_id: dom_id do |c| %> @@ -21,8 +21,13 @@ <% c.with_link { link_to 'View Details', job_path(job) } %> <% else %> <% c.with_body do %> - Nothing is being ripped to <%= ftp_host %> + <% if auto_start_video && auto_start_video.is_a?(Movie) %> + <%= link_to 'Cancel', cancel_auto_start_movie_path(auto_start_video), data: { turbo_method: :post } %> | <%= link_to auto_start_video.title, movie_path(auto_start_video) %> will start ripping + <% else %> + Nothing is being ripped to <%= ftp_host %> + <% end %> <% end %> + <% end %> <% end %> <% end %> diff --git a/app/components/rip_process_component.rb b/app/components/rip_process_component.rb index 37594078..cf045795 100644 --- a/app/components/rip_process_component.rb +++ b/app/components/rip_process_component.rb @@ -5,6 +5,14 @@ def self.job Job.sort_by_created_at.active.find_by(name: 'RipWorker') end + def self.show? + Video.auto_start.any? || job&.active? + end + + def hidden? + !self.class.show? + end + def job self.class.job end @@ -21,6 +29,10 @@ def job_active? job&.active? end + def auto_start_video + @auto_start_video ||= Video.auto_start.first + end + def ftp_host Config::Plex.newest.settings_ftp_host end diff --git a/app/components/scan_plex_process_component.html.erb b/app/components/scan_plex_process_component.html.erb index 57187282..41b3786f 100644 --- a/app/components/scan_plex_process_component.html.erb +++ b/app/components/scan_plex_process_component.html.erb @@ -1,17 +1,21 @@ -<%= render ProcessComponent.new dom_id: dom_id do |c| %> - <%= c.with_title do %> - Scan Plex Status - <% end %> - <%= c.with_body do %> - <% if job_active? %> - <%= render ProgressBarComponent.new( - completed: completed, - status: status, - message: "Scanning #{ftp_host} for movies and tv shows", - ) - %> - <% else %> - Done! you have a total of <%= pluralize(Movie.count, 'movie') %> & <%= pluralize(Tv.count, 'tv show') %> on plex. +<% if hidden? %> + +<% else %> + <%= render ProcessComponent.new dom_id: dom_id do |c| %> + <%= c.with_title do %> + Scan Plex Status + <% end %> + <%= c.with_body do %> + <% if job_active? %> + <%= render ProgressBarComponent.new( + completed: completed, + status: status, + message: "Scanning #{ftp_host} for movies and tv shows", + ) + %> + <% else %> + Done! you have a total of <%= pluralize(Movie.count, 'movie') %> & <%= pluralize(Tv.count, 'tv show') %> on plex. + <% end %> <% end %> <% end %> <% end %> diff --git a/app/components/scan_plex_process_component.rb b/app/components/scan_plex_process_component.rb index c0707f90..36333393 100644 --- a/app/components/scan_plex_process_component.rb +++ b/app/components/scan_plex_process_component.rb @@ -7,6 +7,14 @@ def self.job Job.sort_by_created_at.active.find_by(name: 'ScanPlexWorker') end + def self.show? + job&.active? || false + end + + def hidden? + !self.class.show? + end + def completed job.metadata['completed'].presence || 0.0 end diff --git a/app/components/search_component.html.erb b/app/components/search_component.html.erb index 5990a4af..0d5c22ab 100644 --- a/app/components/search_component.html.erb +++ b/app/components/search_component.html.erb @@ -1,8 +1,8 @@ -<%= simple_form_for search_service, as: :search, url: the_movie_dbs_path, method: :get, html: { class: 'row m-1 pb-4' }, data: { turbo_frame: 'videos', turbo_action: 'replace' } do |f| %> +<%= simple_form_for search_service, as: :search, url: the_movie_dbs_path, method: :get, html: { class: 'row pb-4', style: "margin: auto; width: 50%;" }, data: { turbo_frame: 'videos', turbo_action: 'replace', controller: 'submit-on-keyup' } do |f| %>
- <%= f.input :query, label: 'Search for TV or Movie', autofocus: true, required: false %> + <%= f.input :query, label: 'Search for TV or Movie', autofocus: true, required: false, input_html: { data: { action: "keyup->submit-on-keyup#submitWithDebounce", submit_on_keyup_target: :input } } %>
-
+
<%= f.button :submit, 'Search', data: { disable_with: 'Searching' } %>
<% end %> diff --git a/app/components/upload_process_component.html.erb b/app/components/upload_process_component.html.erb index 39630a43..34b6a864 100644 --- a/app/components/upload_process_component.html.erb +++ b/app/components/upload_process_component.html.erb @@ -1,37 +1,41 @@ -<%= render ProcessComponent.new dom_id: dom_id do |c| %> - <%= c.with_title do %> - Upload Status - <% end %> - <%= c.with_body do %> - <% if uploadable_video_blobs.any? || uploaded_recently_video_blobs.any? %> - <% uploaded_recently_video_blobs.each do |blob| %> - <%= - render( - ProgressBarComponent.new( - show_percentage: false, - status: :success, - completed: 100, - message: "Uploaded #{blob.title}" +<% if hidden? %> + +<% else %> + <%= render ProcessComponent.new dom_id: dom_id, hidden: hidden? do |c| %> + <%= c.with_title do %> + Upload Status + <% end %> + <%= c.with_body do %> + <% if uploadable_video_blobs.any? || uploaded_recently_video_blobs.any? %> + <% uploaded_recently_video_blobs.each do |blob| %> + <%= + render( + ProgressBarComponent.new( + show_percentage: false, + status: :success, + completed: 100, + message: "Uploaded #{blob.title}" + ) ) - ) - %> - <% end %> - <% uploadable_video_blobs.each do |blob| %> - <%= - video_blob_job = find_job_by_video_blob(blob) - render( - ProgressBarComponent.new( - show_percentage: video_blob_job.present?, - status: :info, - completed: percentage(video_blob_job&.completed, blob.byte_size), - message: video_blob_job ? "Uploading #{blob.title}" : "Pending upload #{blob.title}", - eta: (eta(video_blob_job, blob) if video_blob_job) + %> + <% end %> + <% uploadable_video_blobs.each do |blob| %> + <%= + video_blob_job = find_job_by_video_blob(blob) + render( + ProgressBarComponent.new( + show_percentage: video_blob_job.present?, + status: :info, + completed: percentage(video_blob_job&.completed, blob.byte_size), + message: video_blob_job ? "Uploading #{blob.title}" : "Pending upload #{blob.title}", + eta: (eta(video_blob_job, blob) if video_blob_job) + ) ) - ) - %> + %> + <% end %> + <% else %> + Nothing is being uploaded to <%= ftp_host %> <% end %> - <% else %> - Nothing is being uploaded to <%= ftp_host %> <% end %> <% end %> <% end %> diff --git a/app/components/upload_process_component.rb b/app/components/upload_process_component.rb index c33f45cd..e2fe463f 100644 --- a/app/components/upload_process_component.rb +++ b/app/components/upload_process_component.rb @@ -5,6 +5,14 @@ def self.job Job.sort_by_created_at.active.find_by(name: 'UploadWorker') end + def self.show? + VideoBlob.uploaded_recently.any? || VideoBlob.uploadable.any? + end + + def hidden? + !self.class.show? + end + def dom_id 'upload-process-component' end diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index fb92c04d..622b2112 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -26,6 +26,23 @@ def rip redirect_to job_path(job) end + # Call this method to auto start ripping the disk as soon as it the disk title is ready + # It will auto select disk titles for you rather then you selecting them + def auto_start + movie = Movie.find(params[:id]) + Video.auto_start.in_batches { _1.update(auto_start: false) } + movie.update!(auto_start: true) + flash[:notice] = "Once disk is loaded is ready we will start processings #{movie.title}" + redirect_to movie_path(movie) + end + + def cancel_auto_start + movie = Movie.find(params[:id]) + movie.update!(auto_start: false) + flash[:notice] = "Removed #{movie.title} from auto start" + redirect_back_or_to :root + end + private def rip_disk_titles(disk, movie) diff --git a/app/javascript/channels/broadcast_channel.js b/app/javascript/channels/broadcast_channel.js index 4550a6aa..882675b7 100644 --- a/app/javascript/channels/broadcast_channel.js +++ b/app/javascript/channels/broadcast_channel.js @@ -9,5 +9,24 @@ consumer.subscriptions.create('BroadcastChannel', { // }, received(data) { if (data.cableReady) CableReady.perform(data.operations) + + updateSidebarVisibility() } }) + + +function updateSidebarVisibility() { + const sidebar = document.getElementById('sidebar'); + // Check if there are any visible children within the sidebar + const hasVisibleChildren = Array.from(sidebar.children).some(child => { + return child.offsetWidth > 0 && child.offsetHeight > 0 && !child.classList.contains('hidden'); + }); + + if (hasVisibleChildren) { + // If there are visible children, remove the 'hidden' class if it exists + sidebar.classList.remove('hidden'); + } else { + // If no visible children, add the 'hidden' class + sidebar.classList.add('hidden'); + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 523d37bd..43ded407 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -12,3 +12,6 @@ application.register("pagination", PaginationController) import PlexController from "./plex_controller" application.register("plex", PlexController) + +import SubmitOnKeyupController from "./submit_on_keyup_controller" +application.register("submit-on-keyup", SubmitOnKeyupController) diff --git a/app/javascript/controllers/pagination_controller.js b/app/javascript/controllers/pagination_controller.js index 867e3839..086e9695 100644 --- a/app/javascript/controllers/pagination_controller.js +++ b/app/javascript/controllers/pagination_controller.js @@ -5,10 +5,10 @@ export default class extends Controller { connect() { if (!this.hasShowMoreTarget || this.showMoreTarget.dataset.disableWith === undefined) return - console.log("loading"); window.addEventListener("scroll", () => { if (this.showMoreVisable()) this.showMoreTarget.click(); }); + if (this.showMoreVisable()) this.showMoreTarget.click(); } showMoreVisable() { diff --git a/app/javascript/controllers/submit_on_keyup_controller.js b/app/javascript/controllers/submit_on_keyup_controller.js new file mode 100644 index 00000000..53c0d33b --- /dev/null +++ b/app/javascript/controllers/submit_on_keyup_controller.js @@ -0,0 +1,22 @@ +import { Controller } from "@hotwired/stimulus" +import debounce from "lodash/debounce"; + +// Connects to data-controller="submit-on-keyup" +export default class extends Controller { + static targets = ["input"] + + connect() { + this.lastSubmittedValue = this.inputTarget.value; // Initialize with the current input value + this.submitWithDebounce = debounce(this.submitWithDebounce.bind(this), 300); + } + + submitWithDebounce(event) { + event.preventDefault(); + + // Only submit if the current input value is different from the last submitted value + if (this.inputTarget.value !== this.lastSubmittedValue) { + this.lastSubmittedValue = this.inputTarget.value; // Update the last submitted value + this.element.requestSubmit(); + } + } +} diff --git a/app/listeners/disk_listener.rb b/app/listeners/disk_listener.rb index 32a19624..511dfc1d 100644 --- a/app/listeners/disk_listener.rb +++ b/app/listeners/disk_listener.rb @@ -6,36 +6,67 @@ class DiskListener include ActionView::Helpers::UrlHelper include ActionView::Helpers::DateHelper include SlackUtility + include ActionView::Helpers::UrlHelper + delegate :job_url, to: 'Rails.application.routes.url_helpers' delegate :render, to: :ApplicationController def disk_ejecting(disk) - cable_broadcast("Ejecting Disk #{disk.name}") + cable_broadcast(message: "Ejecting Disk #{disk.name}") end def disk_ejected(disk) message = "Disk Ejected #{disk.name} - Ready for new disk" notify_slack(message) - cable_broadcast(message) + cable_broadcast(message:) end def disk_eject_failed(disk, exception) message = "Failed to eject #{disk.name} - #{exception.message}" notify_slack(message) - cable_broadcast(message) + cable_broadcast(message:) end - def disk_loading + def disk_loading(_) cable_broadcast end - def disk_loaded - reload_page! + def disk_loaded(disk) + return reload_page! unless (video = Video.auto_start.first) + + info_disk_titles = rip_disk_titles(disk, video) + return reload_page! if info_disk_titles.empty? + + job = RipWorker.perform_async( + disk_id: disk.id, + disk_title_ids: info_disk_titles.map { _1.disk_title.id }, + extra_types: info_disk_titles.map(&:extra_type) + ) + if job + video.update!(auto_start: false) + redirect_to_job(job) + else + reload_page! + end end private - def cable_broadcast(message = nil) + def redirect_to_job(job) + cable_ready[BroadcastChannel.channel_name].redirect_to(url: job_url(job)) + cable_ready.broadcast + end + + def rip_disk_titles(disk, video) + return [] unless video.is_a?(Movie) + + MovieDiskTitleSelectorService.call(movie: video, disk:).select do |info| + info.disk_title.update!(video:) if info.extra_type.present? + info.extra_type.present? + end + end + + def cable_broadcast(message: nil) component = LoadDiskProcessComponent.new(message:) cable_ready[BroadcastChannel.channel_name].morph( diff --git a/app/listeners/mkv_progress_listener.rb b/app/listeners/mkv_progress_listener.rb index 1f21d72c..6299a950 100644 --- a/app/listeners/mkv_progress_listener.rb +++ b/app/listeners/mkv_progress_listener.rb @@ -90,7 +90,7 @@ def update_job! return if next_update.future? update_progress_bar - job.save! + job.save! if job.changed? @next_update = nil end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 2cb72f9f..c061154a 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -3,6 +3,7 @@ class ApplicationRecord < ActiveRecord::Base include CableReady::Broadcaster + scope :newest, -> { order(created_at: :desc) } self.abstract_class = true def unmark_for_destruction diff --git a/app/models/movie.rb b/app/models/movie.rb index 9708d192..66662611 100644 --- a/app/models/movie.rb +++ b/app/models/movie.rb @@ -5,6 +5,7 @@ # Table name: videos # # id :integer not null, primary key +# auto_start :boolean default(FALSE), not null # backdrop_path :string # episode_distribution_runtime :string # episode_first_air_date :date diff --git a/app/models/tv.rb b/app/models/tv.rb index 8abcaa84..fa623c80 100644 --- a/app/models/tv.rb +++ b/app/models/tv.rb @@ -5,6 +5,7 @@ # Table name: videos # # id :integer not null, primary key +# auto_start :boolean default(FALSE), not null # backdrop_path :string # episode_distribution_runtime :string # episode_first_air_date :date diff --git a/app/models/video.rb b/app/models/video.rb index 0620a6c0..511dd9ee 100644 --- a/app/models/video.rb +++ b/app/models/video.rb @@ -5,6 +5,7 @@ # Table name: videos # # id :integer not null, primary key +# auto_start :boolean default(FALSE), not null # backdrop_path :string # episode_distribution_runtime :string # episode_first_air_date :date @@ -39,6 +40,8 @@ class Video < ApplicationRecord scope :with_video_blobs, -> { includes(:video_blobs).where.not(video_blobs: { id: nil }) } scope :optimized, -> { includes(:optimized_video_blobs).where.not(video_blobs: { id: nil }) } scope :optimized_with_checksum, -> { optimized.merge(VideoBlob.checksum) } + scope :auto_start, -> { where(auto_start: true) } + scope :not_auto_start, -> { where.not(auto_start: true) } validates :title, presence: true @@ -51,18 +54,26 @@ def tv? end def credits + return if the_movie_db_id.nil? + @credits ||= "TheMovieDb::#{type}::Credits".constantize.new(the_movie_db_id).results end def the_movie_db_details + return if the_movie_db_id.nil? + @the_movie_db_details ||= "TheMovieDb::#{type}".constantize.new(the_movie_db_id).results end def release_dates + return if the_movie_db_id.nil? + @release_dates ||= "TheMovieDb::#{type}::ReleaseDates".constantize.new(the_movie_db_id).results end def ratings + return [] if release_dates.nil? + @ratings ||= self.class.ratings.keys & release_dates['results'] .flat_map { _1['release_dates'] } .pluck('certification') diff --git a/app/queries/video_search_query.rb b/app/queries/video_search_query.rb index 33cc0c39..273230dc 100644 --- a/app/queries/video_search_query.rb +++ b/app/queries/video_search_query.rb @@ -5,7 +5,7 @@ class VideoSearchQuery VIDEOS_MEDIA_TYPE = %w[movie tv].freeze RESULTS_PER_PAGE = 21 # the movie db comes back with 20 results per page - option :query, Types::Coercible::String, optional: true + option :query, Types::Coercible::StrippedString, optional: true option :page, Types::Coercible::Integer, default: -> { 1 }, optional: true, null: nil def results diff --git a/app/services/create_disks_service.rb b/app/services/create_disks_service.rb index 0f599c19..9ed22a1f 100644 --- a/app/services/create_disks_service.rb +++ b/app/services/create_disks_service.rb @@ -8,11 +8,7 @@ class CreateDisksService < ApplicationService def call return [] if (drives = list_drives).empty? - disks = drives.map { create_or_update_disks(_1) } - - disks.each { _1.update!(ejected: false) } - broadcast(:disk_loaded) - disks + drives.map { create_or_update_disks(_1) } end private @@ -20,10 +16,11 @@ def call def create_or_update_disks(drive) find_or_initalize_disk(drive).tap do |disk| disk.update!(loading: true) - broadcast(:disk_loading) + broadcast(:disk_loading, disk) disk.disk_titles.each(&:mark_for_destruction) find_or_build_disk_titles(disk) - broadcast(:disk_loaded) + disk.update!(ejected: false) + broadcast(:disk_loaded, disk) ensure disk.update!(loading: false) end diff --git a/app/services/movie_disk_title_selector_service.rb b/app/services/movie_disk_title_selector_service.rb new file mode 100644 index 00000000..b8e05657 --- /dev/null +++ b/app/services/movie_disk_title_selector_service.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +class MovieDiskTitleSelectorService < ApplicationService + Info = Data.define(:disk_title, :extra_type, :within_range, :ripped?, :uploaded?) + + option :disk, Types.Instance(Disk) + option :movie, Types.Instance(Movie) + + def call + disk_title_sorted.map do |disk_title| + Info.new( + disk_title, + extra_type(disk_title), within_range?(disk_title), + ripped?(disk_title), uploaded?(disk_title) + ) + end + end + + private + + def extra_type(disk_title) + if uploaded?(disk_title) + nil + elsif within_range?(disk_title) && feature_film_selected? + 'shorts' + elsif within_range?(disk_title) + feature_film_selected! + 'feature_films' + else + 'other' + end + end + + def ripped?(disk_title) + movie.ripped_disk_titles.any? { _1.name == disk_title.name } + end + + def uploaded?(disk_title) + movie.ripped_disk_titles.find { _1.name == disk_title.name }&.video_blob&.uploaded? || false + end + + def within_range?(disk_title) + movie.runtime_range.include?(disk_title.duration) + end + + def feature_film_selected! + @feature_film_selected = true + end + + def feature_film_selected? + @feature_film_selected == true + end + + def disk_title_sorted + @disk_title_sorted ||= disk.disk_titles.sort_by do |disk_title| + (movie.movie_runtime - disk_title.duration).abs + end + end +end diff --git a/app/views/config/make_mkvs/_form.html.erb b/app/views/config/make_mkvs/_form.html.erb index a6c729a4..ba8df111 100644 --- a/app/views/config/make_mkvs/_form.html.erb +++ b/app/views/config/make_mkvs/_form.html.erb @@ -13,7 +13,7 @@ Install MKV <% end %>
-
+
<%= f.button :submit %>
diff --git a/app/views/jobs/show.html.erb b/app/views/jobs/show.html.erb index a7f46dbc..902b55af 100644 --- a/app/views/jobs/show.html.erb +++ b/app/views/jobs/show.html.erb @@ -1,4 +1,4 @@ -<% content_for :sidebar_before do %> +<% content_for :sidebar do %>

diff --git a/app/views/layouts/_bg_processes.html.erb b/app/views/layouts/_bg_processes.html.erb new file mode 100644 index 00000000..6521a9df --- /dev/null +++ b/app/views/layouts/_bg_processes.html.erb @@ -0,0 +1,5 @@ +
+ <% [RipProcessComponent, ScanPlexProcessComponent, LoadDiskProcessComponent, UploadProcessComponent].each do |component| %> + <%= render component.new %> + <% end %> +
diff --git a/app/views/layouts/_bg_progress.erb b/app/views/layouts/_bg_progress.erb deleted file mode 100644 index ea830ebe..00000000 --- a/app/views/layouts/_bg_progress.erb +++ /dev/null @@ -1,5 +0,0 @@ -
- <% [ScanPlexProcessComponent, LoadDiskProcessComponent, UploadProcessComponent, RipProcessComponent].sort_by { _1.job == @job ? 0 : 1 }.each do |klass| %> - <%= render klass.new %> - <% end %> -
diff --git a/app/views/layouts/_content.html.erb b/app/views/layouts/_content.html.erb new file mode 100644 index 00000000..909ab588 --- /dev/null +++ b/app/views/layouts/_content.html.erb @@ -0,0 +1,17 @@ +
+
+ <% if content_for?(:sidebar) %> + + <% else %> + <%= render "layouts/bg_processes", classes: ['fixed', 'compact'] %> + <% end %> + +
+ <%= bootstrap_alerts %> + <%= yield %> +
+
+
diff --git a/app/views/layouts/application.erb b/app/views/layouts/application.erb index 550a8f4b..0ca753f7 100644 --- a/app/views/layouts/application.erb +++ b/app/views/layouts/application.erb @@ -6,27 +6,7 @@ <%= render 'layouts/head' %> -
- <%= render 'layouts/header' %> -
-
- <% if content_for? :sidebar_before %> - - <% end %> - <%= render 'layouts/bg_progress' %> - <% if content_for? :sidebar_after %> - - <% end %> -
-
- <%= bootstrap_alerts %> - <%= yield %> -
-
-
+ <%= render 'layouts/header' %> + <%= render "layouts/content" %> diff --git a/app/views/movies/show.html.erb b/app/views/movies/show.html.erb index ef28cc53..e91a6aa8 100644 --- a/app/views/movies/show.html.erb +++ b/app/views/movies/show.html.erb @@ -1,30 +1,49 @@ <%= render SearchComponent.new %> -
-
+ +<% content_for :sidebar, '' %> + +
+
<%= imdb_image_tag @movie.poster_path, klass: 'img-poster', width: 400 %>
-
-
-
- <%= icon('film', type: 'fas') %> - <%= @movie.title %> -
- <%= @movie.original_title %> -

<%= @movie.overview %>

-

- - <%= @movie.release_or_air_date %> - <%= link_to_movie_db_movie @movie.the_movie_db_id %> - -

- <% @movie.video_blobs.uploadable.uploaded.each do |blob| %> -

<%= blob.key %>

+
+

+ <%= icon('film', type: 'fas') %> + <%= link_to_movie_db_movie @movie.the_movie_db_id, text: @movie.plex_name %>

+
+ <%= @movie.ratings.reject { _1 == 'NR' }.first&.strip %> + <% if @movie.release_or_air_date %> + <%= l @movie.release_or_air_date, format: :long %> + <% end %> + <% if @movie.the_movie_db_details %> + <% @movie.the_movie_db_details['genres'].each do |genre| %> + <%= genre["name"].strip %> + <% end %> <% end %> -

Blue Means the disk title is within the runtime range of the movie.

-

Gream Means the disk has been ripped before.

+
+
+

<%= @movie.overview %>

+
+ <%= render CastComponent.new(video: @movie) %> +
+ <%= render MovieTitleTableComponent.new(disks: @disks, movie: @movie) %> +
+
+ <% if Job.sort_by_created_at.active.find_by(name: 'LoadDiskWorker')&.active? %> +

+ Stand by the disk is still being loading from the CD drive this could take a while. Page will update once the disk is ready so no need to refresh the page but if you do it won't hurt anything. +

+ <% else %> +

+ Please refresh the page if you see this warning but are certain a disk is present, as no disk is currently loaded or detected. +

+ <% end %> +
+
    + <% @movie.video_blobs.each do |video_blob| %> +
  • <%= video_blob.plex_path %>
  • + <% end %> +
-
- <%= render MovieTitleTableComponent.new(disks: @disks, movie: @movie) %> -
diff --git a/app/views/seasons/show.html.erb b/app/views/seasons/show.html.erb index a11c9a79..49675572 100644 --- a/app/views/seasons/show.html.erb +++ b/app/views/seasons/show.html.erb @@ -74,7 +74,7 @@ <% end %> -
+
<%= f.button :submit, 'Rip Disk' %>
diff --git a/app/views/the_movie_dbs/_cast.html.erb b/app/views/the_movie_dbs/_cast.html.erb deleted file mode 100644 index 0b4157da..00000000 --- a/app/views/the_movie_dbs/_cast.html.erb +++ /dev/null @@ -1,20 +0,0 @@ -
-
- <%= render 'cast', video: video %> -
-
- -<% if video.credits['cast'].any? %> -

Cast

-
    - <% video.credits['cast'].each do |cast| %> -
  1. - <%= imdb_image_tag cast['profile_path'], klass: 'card-img-top rounded-0' %> -
    -
    <%= cast['name'] %>
    -

    <%= cast['character'] %>

    -
    -
  2. - <% end %> -
-<% end %> diff --git a/app/views/the_movie_dbs/_videos.html.erb b/app/views/the_movie_dbs/_videos.html.erb index b2c18982..216c9436 100644 --- a/app/views/the_movie_dbs/_videos.html.erb +++ b/app/views/the_movie_dbs/_videos.html.erb @@ -1,21 +1,17 @@ <%= turbo_frame_tag 'videos', class: 'display-flex' do %> - <% if params.dig(:search, :query).blank? %> + <% if params.dig(:search, :query).blank? && search_service.results.none?%>
-
-

To Getting Started

-

Type in the name of the movie or TV show your looking for into the search bar above.

-
+

To Getting Started

+

Type in the name of the movie or TV show your looking for into the search bar above.

<% end %> <% if search_service.results.none? && params.dig(:search, :query).present? %>
-
-

No Results Found For <%= params.dig(:search, :query) %>

-

Try typing a different movie name

-
+

No Results Found For <%= params.dig(:search, :query) %>

+

Try typing a different movie name

<% else %> diff --git a/app/views/the_movie_dbs/index.html.erb b/app/views/the_movie_dbs/index.html.erb index c099d276..4268033c 100644 --- a/app/views/the_movie_dbs/index.html.erb +++ b/app/views/the_movie_dbs/index.html.erb @@ -1,10 +1,2 @@ - -<%= simple_form_for search_service, as: :search, url: the_movie_dbs_path, method: :get, html: { class: 'row m-1 pb-4' }, data: { turbo_frame: 'videos', turbo_action: 'replace' } do |f| %> -
- <%= f.input :query, label: 'Search for TV or Movie', autofocus: true, required: false %> -
-
- <%= f.button :submit, 'Search', data: { disable_with: 'Searching' } %> -
-<% end %> +<%= render SearchComponent.new(search_service:) %> <%= render partial: 'videos', locals: { search_service: } %> diff --git a/app/workers/rip_worker.rb b/app/workers/rip_worker.rb index 3e73171a..99bfd560 100644 --- a/app/workers/rip_worker.rb +++ b/app/workers/rip_worker.rb @@ -36,7 +36,7 @@ def create_mkvs def eject_disk service = EjectDiskService.new(disk) - service.subscribe(DiskListener.new(disk:)) + service.subscribe(DiskListener.new) service.call end diff --git a/config/initializers/types.rb b/config/initializers/types.rb index 2669e9ef..bc5ee902 100644 --- a/config/initializers/types.rb +++ b/config/initializers/types.rb @@ -2,4 +2,5 @@ module Types include Dry.Types() + Coercible::StrippedString = Coercible::String.constructor { |value| value.to_s.strip } end diff --git a/config/locales/en.yml b/config/locales/en.yml index cf9b342d..7f642961 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -30,4 +30,8 @@ # available at https://guides.rubyonrails.org/i18n.html. en: - hello: "Hello world" + time: + formats: + default: "%Y-%m-%d %H:%M:%S" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" diff --git a/config/routes.rb b/config/routes.rb index 398fbaa1..3efbf7f6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -30,7 +30,11 @@ # Movies # #--------# resources :movies do - member { post :rip } + member do + post :rip + post :auto_start + post :cancel_auto_start + end end #----------# diff --git a/current_version.txt b/current_version.txt index 51be872e..9ca23982 100644 --- a/current_version.txt +++ b/current_version.txt @@ -1 +1 @@ -v4.2.0 +v4.3.0 diff --git a/db/migrate/20240830212716_add_auto_start_to_disk.rb b/db/migrate/20240830212716_add_auto_start_to_disk.rb new file mode 100644 index 00000000..f69e5c76 --- /dev/null +++ b/db/migrate/20240830212716_add_auto_start_to_disk.rb @@ -0,0 +1,5 @@ +class AddAutoStartToDisk < ActiveRecord::Migration[7.2] + def change + add_column :videos, :auto_start, :boolean, default: false, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index b488312b..22aac111 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_07_16_170733) do +ActiveRecord::Schema[7.2].define(version: 2024_08_30_212716) do create_table "configs", force: :cascade do |t| t.string "type", default: "Config", null: false t.text "settings" @@ -156,7 +156,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "rating", default: 0, null: false + t.boolean "auto_start", default: false, null: false t.index ["type", "the_movie_db_id"], name: "index_videos_on_type_and_the_movie_db_id", unique: true end - end diff --git a/spec/components/cast_component_spec.rb b/spec/components/cast_component_spec.rb new file mode 100644 index 00000000..a4afe8b4 --- /dev/null +++ b/spec/components/cast_component_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe CastComponent, type: :component do + before do + create(:config_the_movie_db) + render_inline(described_class.new(video:)) + end + + describe 'render' do + context 'when video is a movie', vcr: { record: :once, cassette_name: "#{described_class}/render_movie" } do + let(:video) { create(:movie, title: 'Frosty the Snowman', the_movie_db_id: 13_675) } + + it 'renders something useful' do + expect(rendered_content).to include('Karen (uncredited)') + end + end + + context 'when video is a tv show', vcr: { record: :once, cassette_name: "#{described_class}/render_tv" } do + let(:video) { create(:tv, title: 'Firefly', the_movie_db_id: 72_893) } + + it 'renders something useful' do + expect(rendered_content).to include('Aslı Eğilmez') + end + end + + context 'when the movie has not cast info' do + let(:video) { create(:movie, title: 'Frosty the Snowman', the_movie_db_id: nil) } + + it 'renders nothing' do + expect(rendered_content).to eq '' + end + end + end +end diff --git a/spec/components/load_disk_process_component_spec.rb b/spec/components/load_disk_process_component_spec.rb index 68dbf56b..d5465a8d 100644 --- a/spec/components/load_disk_process_component_spec.rb +++ b/spec/components/load_disk_process_component_spec.rb @@ -3,9 +3,22 @@ require 'rails_helper' RSpec.describe LoadDiskProcessComponent, type: :component do - before { render_inline(described_class.new) } + let(:component) { described_class.new } + + before { render_inline(component) } it 'renders something useful' do - expect(rendered_content).to include('No disks found - insert disk to continue') + expect(rendered_content).to include('') + end + + context 'when hidden is false' do + let(:component) do + allow(described_class).to receive(:show?).and_return(true) + described_class.new + end + + it 'show the component' do + expect(rendered_content).to include('No disks found - insert disk to continue') + end end end diff --git a/spec/factories/movies.rb b/spec/factories/movies.rb index aafaaa73..658165b0 100644 --- a/spec/factories/movies.rb +++ b/spec/factories/movies.rb @@ -5,6 +5,7 @@ # Table name: videos # # id :integer not null, primary key +# auto_start :boolean default(FALSE), not null # backdrop_path :string # episode_distribution_runtime :string # episode_first_air_date :date diff --git a/spec/factories/tvs.rb b/spec/factories/tvs.rb index 8a861e67..ef0de806 100644 --- a/spec/factories/tvs.rb +++ b/spec/factories/tvs.rb @@ -5,6 +5,7 @@ # Table name: videos # # id :integer not null, primary key +# auto_start :boolean default(FALSE), not null # backdrop_path :string # episode_distribution_runtime :string # episode_first_air_date :date diff --git a/spec/factories/videos.rb b/spec/factories/videos.rb index 02f1d24a..a05a0dff 100644 --- a/spec/factories/videos.rb +++ b/spec/factories/videos.rb @@ -5,6 +5,7 @@ # Table name: videos # # id :integer not null, primary key +# auto_start :boolean default(FALSE), not null # backdrop_path :string # episode_distribution_runtime :string # episode_first_air_date :date diff --git a/spec/fixtures/vcr_cassettes/CastComponent/render_movie.yml b/spec/fixtures/vcr_cassettes/CastComponent/render_movie.yml new file mode 100644 index 00000000..68ffead6 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/CastComponent/render_movie.yml @@ -0,0 +1,63 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.themoviedb.org/3/movie/13675/credits?api_key= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.11.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json;charset=utf-8 + Content-Length: + - '6305' + Connection: + - keep-alive + Date: + - Tue, 03 Sep 2024 21:47:12 GMT + Server: + - openresty + Cache-Control: + - public, max-age=13832 + X-Memc: + - HIT + X-Memc-Key: + - 686edd7b8e284df5248e358d4e1c1b95 + X-Memc-Age: + - '11684' + X-Memc-Expires: + - '13832' + Etag: + - W/"8cb72958d0b56231119c61fe5bf12ef5" + Vary: + - Accept-Encoding,accept-encoding + - Origin + X-Cache: + - Hit from cloudfront + Via: + - 1.1 389aa4b0b70bc5506cb9535516428a34.cloudfront.net (CloudFront) + X-Amz-Cf-Pop: + - LAX53-P1 + Alt-Svc: + - h3=":443"; ma=86400 + X-Amz-Cf-Id: + - uGmItJKwEtzeaXoRVt_JJ6IgE6WG0eWq8EVXHcCkzT2HKO_f3TJggw== + Age: + - '6495' + body: + encoding: ASCII-8BIT + string: !binary |- + eyJpZCI6MTM2NzUsImNhc3QiOlt7ImFkdWx0IjpmYWxzZSwiZ2VuZGVyIjoyLCJpZCI6MTIzODkzLCJrbm93bl9mb3JfZGVwYXJ0bWVudCI6IkFjdGluZyIsIm5hbWUiOiJKYWNraWUgVmVybm9uIiwib3JpZ2luYWxfbmFtZSI6IkphY2tpZSBWZXJub24iLCJwb3B1bGFyaXR5Ijo0LjczNiwicHJvZmlsZV9wYXRoIjoiLzQ3QTQwMzAxT2VCOWFYV1ZZT2trVkh1Q1owUy5qcGciLCJjYXN0X2lkIjo1LCJjaGFyYWN0ZXIiOiJGcm9zdHkgdGhlIFNub3dtYW4gKHZvaWNlKSIsImNyZWRpdF9pZCI6IjUyZmU0NThhOTI1MTQxNmM3NTA1YTEyMSIsIm9yZGVyIjowfSx7ImFkdWx0IjpmYWxzZSwiZ2VuZGVyIjoyLCJpZCI6MzQ3NDUsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiQWN0aW5nIiwibmFtZSI6IkppbW15IER1cmFudGUiLCJvcmlnaW5hbF9uYW1lIjoiSmltbXkgRHVyYW50ZSIsInBvcHVsYXJpdHkiOjEwLjY2NCwicHJvZmlsZV9wYXRoIjoiL2xZWjdYNjBRclJlNkN4bVdhSkpBaXNIejdwMy5qcGciLCJjYXN0X2lkIjozLCJjaGFyYWN0ZXIiOiJOYXJyYXRvciAodm9pY2UpIiwiY3JlZGl0X2lkIjoiNTJmZTQ1OGE5MjUxNDE2Yzc1MDVhMTE5Iiwib3JkZXIiOjF9LHsiYWR1bHQiOmZhbHNlLCJnZW5kZXIiOjIsImlkIjoxMTAxMzUsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiQWN0aW5nIiwibmFtZSI6IkJpbGx5IERlIFdvbGZlIiwib3JpZ2luYWxfbmFtZSI6IkJpbGx5IERlIFdvbGZlIiwicG9wdWxhcml0eSI6Ny40ODQsInByb2ZpbGVfcGF0aCI6Ii9rVVdGN0Y4eDJSeUxUOEYxelRhN1FoWTNIaFYuanBnIiwiY2FzdF9pZCI6NCwiY2hhcmFjdGVyIjoiUHJvZmVzc29yIEhpbmtsZSAodm9pY2UpIiwiY3JlZGl0X2lkIjoiNTJmZTQ1OGE5MjUxNDE2Yzc1MDVhMTFkIiwib3JkZXIiOjJ9LHsiYWR1bHQiOmZhbHNlLCJnZW5kZXIiOjIsImlkIjoxNjQxNywia25vd25fZm9yX2RlcGFydG1lbnQiOiJBY3RpbmciLCJuYW1lIjoiUGF1bCBGcmVlcyIsIm9yaWdpbmFsX25hbWUiOiJQYXVsIEZyZWVzIiwicG9wdWxhcml0eSI6MTUuNzEyLCJwcm9maWxlX3BhdGgiOiIvcEFDVDBMWWtFeWZmN1BHdDg4aXFyNXU5VDd1LmpwZyIsImNhc3RfaWQiOjYsImNoYXJhY3RlciI6IlNhbnRhIENsYXVzIC8gVHJhZmZpYyBDb3AgLyBBZGRpdGlvbmFsIFZvaWNlcyAodm9pY2UpIiwiY3JlZGl0X2lkIjoiNTJmZTQ1OGE5MjUxNDE2Yzc1MDVhMTI1Iiwib3JkZXIiOjN9LHsiYWR1bHQiOmZhbHNlLCJnZW5kZXIiOjEsImlkIjoxNTA5OCwia25vd25fZm9yX2RlcGFydG1lbnQiOiJBY3RpbmciLCJuYW1lIjoiSnVuZSBGb3JheSIsIm9yaWdpbmFsX25hbWUiOiJKdW5lIEZvcmF5IiwicG9wdWxhcml0eSI6MTEuODcxLCJwcm9maWxlX3BhdGgiOiIvNnM1c1djT1BHdFZkYk1GdFp3a1dhZEFyajJJLmpwZyIsImNhc3RfaWQiOjcsImNoYXJhY3RlciI6IlRlYWNoZXIgLyBLYXJlbiAvIEFkZGl0aW9uYWwgVm9pY2VzICh2b2ljZSkiLCJjcmVkaXRfaWQiOiI1MmZlNDU4YTkyNTE0MTZjNzUwNWExMjkiLCJvcmRlciI6NH0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MSwiaWQiOjExMjkxOTMsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiQWN0aW5nIiwibmFtZSI6IlN1emFubmUgRGF2aWRzb24iLCJvcmlnaW5hbF9uYW1lIjoiU3V6YW5uZSBEYXZpZHNvbiIsInBvcHVsYXJpdHkiOjEuMDI0LCJwcm9maWxlX3BhdGgiOm51bGwsImNhc3RfaWQiOjE4LCJjaGFyYWN0ZXIiOiJLYXJlbiAodW5jcmVkaXRlZCkiLCJjcmVkaXRfaWQiOiI2MWIzOTVjN2VjMTg3OTAwMWM2OWI4ODgiLCJvcmRlciI6NX0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MCwiaWQiOjI1ODg1MTYsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiQWN0aW5nIiwibmFtZSI6IkdyZWcgVGhvbWFzIiwib3JpZ2luYWxfbmFtZSI6IkdyZWcgVGhvbWFzIiwicG9wdWxhcml0eSI6Mi4zMjIsInByb2ZpbGVfcGF0aCI6bnVsbCwiY2FzdF9pZCI6MTksImNoYXJhY3RlciI6IkthcmVuJ3MgZnJpZW5kcyAodW5jcmVkaXRlZCkiLCJjcmVkaXRfaWQiOiI2MWIzOTYwYTRlNjc0MjAwNWZkZjEzNzQiLCJvcmRlciI6Nn1dLCJjcmV3IjpbeyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MiwiaWQiOjE2NDEwLCJrbm93bl9mb3JfZGVwYXJ0bWVudCI6IkRpcmVjdGluZyIsIm5hbWUiOiJKdWxlcyBCYXNzIiwib3JpZ2luYWxfbmFtZSI6Ikp1bGVzIEJhc3MiLCJwb3B1bGFyaXR5Ijo3LjMyMiwicHJvZmlsZV9wYXRoIjoiL2NWeUZoWFhTbUVXa1JjSjRPNjJ3akxCbHNYRi5qcGciLCJjcmVkaXRfaWQiOiI1MmZlNDU4YTkyNTE0MTZjNzUwNWExMGYiLCJkZXBhcnRtZW50IjoiRGlyZWN0aW5nIiwiam9iIjoiRGlyZWN0b3IifSx7ImFkdWx0IjpmYWxzZSwiZ2VuZGVyIjoyLCJpZCI6MTY0MTEsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiRGlyZWN0aW5nIiwibmFtZSI6IkFydGh1ciBSYW5raW4sIEpyLiIsIm9yaWdpbmFsX25hbWUiOiJBcnRodXIgUmFua2luLCBKci4iLCJwb3B1bGFyaXR5Ijo1Ljg0MiwicHJvZmlsZV9wYXRoIjoiL2VhOEVDMDRDOVZoRGQwaU1wSXlDbVpIbk1hay5qcGciLCJjcmVkaXRfaWQiOiI1MmZlNDU4YTkyNTE0MTZjNzUwNWExMTUiLCJkZXBhcnRtZW50IjoiRGlyZWN0aW5nIiwiam9iIjoiRGlyZWN0b3IifSx7ImFkdWx0IjpmYWxzZSwiZ2VuZGVyIjoyLCJpZCI6MTA1NjM5LCJrbm93bl9mb3JfZGVwYXJ0bWVudCI6IldyaXRpbmciLCJuYW1lIjoiUm9tZW8gTXVsbGVyIiwib3JpZ2luYWxfbmFtZSI6IlJvbWVvIE11bGxlciIsInBvcHVsYXJpdHkiOjguMjgyLCJwcm9maWxlX3BhdGgiOm51bGwsImNyZWRpdF9pZCI6IjYxODFmMjY1MzlhMWE2MDA2NGU4OWNjYyIsImRlcGFydG1lbnQiOiJXcml0aW5nIiwiam9iIjoiV3JpdGVyIn0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MCwiaWQiOjExMjUyOTYsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiRWRpdGluZyIsIm5hbWUiOiJJcndpbiBHb2xkcmVzcyIsIm9yaWdpbmFsX25hbWUiOiJJcndpbiBHb2xkcmVzcyIsInBvcHVsYXJpdHkiOjAuMDAxLCJwcm9maWxlX3BhdGgiOm51bGwsImNyZWRpdF9pZCI6IjYxODFmMjc0ZTU0ZDVkMDA0M2FiYjczNSIsImRlcGFydG1lbnQiOiJFZGl0aW5nIiwiam9iIjoiRWRpdG9yIn0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MCwiaWQiOjMyOTQ5ODgsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiU291bmQiLCJuYW1lIjoiUGhpbCBLYXllIiwib3JpZ2luYWxfbmFtZSI6IlBoaWwgS2F5ZSIsInBvcHVsYXJpdHkiOjAuMDAxLCJwcm9maWxlX3BhdGgiOm51bGwsImNyZWRpdF9pZCI6IjYxODFmMmNiMTNhMzg4MDAyYjBlMzAzYiIsImRlcGFydG1lbnQiOiJTb3VuZCIsImpvYiI6IlNvdW5kIEVuZ2luZWVyIn0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MCwiaWQiOjIwMTAzMTAsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiU291bmQiLCJuYW1lIjoiRXJpYyBUb21saW5zb24iLCJvcmlnaW5hbF9uYW1lIjoiRXJpYyBUb21saW5zb24iLCJwb3B1bGFyaXR5IjowLjE5NSwicHJvZmlsZV9wYXRoIjpudWxsLCJjcmVkaXRfaWQiOiI2MTgxZjJmZGFmNmU5NDAwNDMzYzRjMmMiLCJkZXBhcnRtZW50IjoiU291bmQiLCJqb2IiOiJTb3VuZCBSZWNvcmRpc3QifSx7ImFkdWx0IjpmYWxzZSwiZ2VuZGVyIjoyLCJpZCI6MTEyNTI5Nywia25vd25fZm9yX2RlcGFydG1lbnQiOiJBcnQiLCJuYW1lIjoiUGF1bCBDb2tlciBKci4iLCJvcmlnaW5hbF9uYW1lIjoiUGF1bCBDb2tlciBKci4iLCJwb3B1bGFyaXR5IjoyLjYyNSwicHJvZmlsZV9wYXRoIjpudWxsLCJjcmVkaXRfaWQiOiI2MTgxZjMwY2M3YzIyNDAwOTM5MzFkNGQiLCJkZXBhcnRtZW50IjoiVmlzdWFsIEVmZmVjdHMiLCJqb2IiOiJDaGFyYWN0ZXIgRGVzaWduZXIifSx7ImFkdWx0IjpmYWxzZSwiZ2VuZGVyIjoyLCJpZCI6MTY0MTUsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiU291bmQiLCJuYW1lIjoiTWF1cnkgTGF3cyIsIm9yaWdpbmFsX25hbWUiOiJNYXVyeSBMYXdzIiwicG9wdWxhcml0eSI6Mi41ODcsInByb2ZpbGVfcGF0aCI6Ii9tQkd1cWJ4YjFOUTB3UkJWSDBEZDA5NjBPai5qcGciLCJjcmVkaXRfaWQiOiI2MTgxZjM0M2QyMDdmMzAwNDM5NzNlNTUiLCJkZXBhcnRtZW50IjoiU291bmQiLCJqb2IiOiJNdXNpYyBEaXJlY3RvciJ9LHsiYWR1bHQiOmZhbHNlLCJnZW5kZXIiOjIsImlkIjo5NzI4OCwia25vd25fZm9yX2RlcGFydG1lbnQiOiJEaXJlY3RpbmciLCJuYW1lIjoiT3NhbXUgRGV6YWtpIiwib3JpZ2luYWxfbmFtZSI6Ik9zYW11IERlemFraSIsInBvcHVsYXJpdHkiOjkuNjIsInByb2ZpbGVfcGF0aCI6Ii90dmRDOFhQa282eEtNRTRja05QMVlzQlVOWkouanBnIiwiY3JlZGl0X2lkIjoiNjFhYmNmYzNhNmRkY2IwMDY4OTYzY2RlIiwiZGVwYXJ0bWVudCI6IlZpc3VhbCBFZmZlY3RzIiwiam9iIjoiQW5pbWF0aW9uIERpcmVjdG9yIn0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MiwiaWQiOjE2NDEwLCJrbm93bl9mb3JfZGVwYXJ0bWVudCI6IkRpcmVjdGluZyIsIm5hbWUiOiJKdWxlcyBCYXNzIiwib3JpZ2luYWxfbmFtZSI6Ikp1bGVzIEJhc3MiLCJwb3B1bGFyaXR5Ijo3LjMyMiwicHJvZmlsZV9wYXRoIjoiL2NWeUZoWFhTbUVXa1JjSjRPNjJ3akxCbHNYRi5qcGciLCJjcmVkaXRfaWQiOiI2MWIzOTYzMWVjMTg3OTAwNjFhOWM0MWIiLCJkZXBhcnRtZW50IjoiUHJvZHVjdGlvbiIsImpvYiI6IlByb2R1Y2VyIn0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MiwiaWQiOjE2NDExLCJrbm93bl9mb3JfZGVwYXJ0bWVudCI6IkRpcmVjdGluZyIsIm5hbWUiOiJBcnRodXIgUmFua2luLCBKci4iLCJvcmlnaW5hbF9uYW1lIjoiQXJ0aHVyIFJhbmtpbiwgSnIuIiwicG9wdWxhcml0eSI6NS44NDIsInByb2ZpbGVfcGF0aCI6Ii9lYThFQzA0QzlWaERkMGlNcEl5Q21aSG5NYWsuanBnIiwiY3JlZGl0X2lkIjoiNjFiMzk2NDY5NmUzMGIwMDkxMGRlODAwIiwiZGVwYXJ0bWVudCI6IlByb2R1Y3Rpb24iLCJqb2IiOiJQcm9kdWNlciJ9LHsiYWR1bHQiOmZhbHNlLCJnZW5kZXIiOjAsImlkIjozMzQyNzk5LCJrbm93bl9mb3JfZGVwYXJ0bWVudCI6IlNvdW5kIiwibmFtZSI6IkJvYiBFbGRlciIsIm9yaWdpbmFsX25hbWUiOiJCb2IgRWxkZXIiLCJwb3B1bGFyaXR5IjowLjAwMSwicHJvZmlsZV9wYXRoIjpudWxsLCJjcmVkaXRfaWQiOiI2MWIzOTY5NDc0ZDZjMDAwOTFkZWIxY2YiLCJkZXBhcnRtZW50IjoiU291bmQiLCJqb2IiOiJTb3VuZCBSZS1SZWNvcmRpbmcgTWl4ZXIifSx7ImFkdWx0IjpmYWxzZSwiZ2VuZGVyIjowLCJpZCI6MzEwNDgxMCwia25vd25fZm9yX2RlcGFydG1lbnQiOiJWaXN1YWwgRWZmZWN0cyIsIm5hbWUiOiJTdGV2ZSBOYWthZ2F3YSIsIm9yaWdpbmFsX25hbWUiOiJTdGV2ZSBOYWthZ2F3YSIsInBvcHVsYXJpdHkiOjAuMDAxLCJwcm9maWxlX3BhdGgiOm51bGwsImNyZWRpdF9pZCI6IjY1N2U2NDgxMzk0YTg3MDg1MzczM2RlYiIsImRlcGFydG1lbnQiOiJWaXN1YWwgRWZmZWN0cyIsImpvYiI6IkFuaW1hdGlvbiBTdXBlcnZpc29yIn0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MiwiaWQiOjM0NzQ1LCJrbm93bl9mb3JfZGVwYXJ0bWVudCI6IkFjdGluZyIsIm5hbWUiOiJKaW1teSBEdXJhbnRlIiwib3JpZ2luYWxfbmFtZSI6IkppbW15IER1cmFudGUiLCJwb3B1bGFyaXR5IjoxMC42NjQsInByb2ZpbGVfcGF0aCI6Ii9sWVo3WDYwUXJSZTZDeG1XYUpKQWlzSHo3cDMuanBnIiwiY3JlZGl0X2lkIjoiNjU5MzgwZDE2NTFmY2Y1ZWJhOGZhODUyIiwiZGVwYXJ0bWVudCI6IlNvdW5kIiwiam9iIjoiVGhlbWUgU29uZyBQZXJmb3JtYW5jZSJ9LHsiYWR1bHQiOmZhbHNlLCJnZW5kZXIiOjIsImlkIjoxMDI1NzE4LCJrbm93bl9mb3JfZGVwYXJ0bWVudCI6IlZpc3VhbCBFZmZlY3RzIiwibmFtZSI6IkFraW8gU3VnaW5vIiwib3JpZ2luYWxfbmFtZSI6IuadiemHjuaYreWkqyIsInBvcHVsYXJpdHkiOjMuNjAyLCJwcm9maWxlX3BhdGgiOm51bGwsImNyZWRpdF9pZCI6IjY1YTk4M2NlZDk1NDIwMDBjYzIwZmJkNyIsImRlcGFydG1lbnQiOiJWaXN1YWwgRWZmZWN0cyIsImpvYiI6IktleSBBbmltYXRpb24ifSx7ImFkdWx0IjpmYWxzZSwiZ2VuZGVyIjoyLCJpZCI6MTU5OTQ4Miwia25vd25fZm9yX2RlcGFydG1lbnQiOiJTb3VuZCIsIm5hbWUiOiJKaW0gSGFycmlzIiwib3JpZ2luYWxfbmFtZSI6IkppbSBIYXJyaXMiLCJwb3B1bGFyaXR5IjowLjAwMSwicHJvZmlsZV9wYXRoIjpudWxsLCJjcmVkaXRfaWQiOiI2NjhlMmM5MDc2NzlkOTk1OWE1YzI5MDEiLCJkZXBhcnRtZW50IjoiU291bmQiLCJqb2IiOiJTb3VuZCBFbmdpbmVlciJ9XX0= + recorded_at: Tue, 03 Sep 2024 23:35:27 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/CastComponent/render_tv.yml b/spec/fixtures/vcr_cassettes/CastComponent/render_tv.yml new file mode 100644 index 00000000..979cd14a --- /dev/null +++ b/spec/fixtures/vcr_cassettes/CastComponent/render_tv.yml @@ -0,0 +1,61 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.themoviedb.org/3/tv/72893/credits?api_key= + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.11.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json;charset=utf-8 + Content-Length: + - '3851' + Connection: + - keep-alive + Date: + - Tue, 03 Sep 2024 23:34:54 GMT + Server: + - openresty + Cache-Control: + - public, max-age=10503 + X-Memc: + - HIT + X-Memc-Key: + - 1d8719969716d0c610f2181c0df5c5f4 + X-Memc-Age: + - '17805' + X-Memc-Expires: + - '10503' + Etag: + - W/"d238f80fa5d9dc33fb4b6dbe32d76a6d" + Vary: + - Accept-Encoding,accept-encoding + - Origin + X-Cache: + - Miss from cloudfront + Via: + - 1.1 3d4704605b9b7f44c7958c0627a493d6.cloudfront.net (CloudFront) + X-Amz-Cf-Pop: + - LAX53-P1 + Alt-Svc: + - h3=":443"; ma=86400 + X-Amz-Cf-Id: + - LNZY1Ybm7x3J6ZFj16lrA66GFBaDU1-93h98v0GCGd3G_B1MmjrhFA== + body: + encoding: ASCII-8BIT + string: !binary |- + eyJjYXN0IjpbeyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MiwiaWQiOjE1NzUwMTQsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiQWN0aW5nIiwibmFtZSI6IlNlw6draW4gw5Z6ZGVtaXIiLCJvcmlnaW5hbF9uYW1lIjoiU2XDp2tpbiDDlnpkZW1pciIsInBvcHVsYXJpdHkiOjEwLjUxNSwicHJvZmlsZV9wYXRoIjoiL212ZjFaUHl4YURLTHpmcnRyMG9Yb21xNkdZNi5qcGciLCJjaGFyYWN0ZXIiOiJCYXLEscWfIEJ1a2EiLCJjcmVkaXRfaWQiOiI1YWVlMjA1NTkyNTE0MTE5NTQwMDBjYjYiLCJvcmRlciI6MH0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MSwiaWQiOjE0NjQ1MDEsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiQWN0aW5nIiwibmFtZSI6Ik5pbGF5IERlbml6Iiwib3JpZ2luYWxfbmFtZSI6Ik5pbGF5IERlbml6IiwicG9wdWxhcml0eSI6MTUuNzMsInByb2ZpbGVfcGF0aCI6Ii9pUExEZUJaNmRpMTE2YkdUWUY2VzRURG4yQ1guanBnIiwiY2hhcmFjdGVyIjoiQXNsxLEgRcSfaWxtZXoiLCJjcmVkaXRfaWQiOiI1YWVlMjA0OTkyNTE0MTE5NWUwMDEwYWUiLCJvcmRlciI6MX0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MSwiaWQiOjEyNTYzNzIsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiQWN0aW5nIiwibmFtZSI6IlNlZGEgR8O8dmVuIiwib3JpZ2luYWxfbmFtZSI6IlNlZGEgR8O8dmVuIiwicG9wdWxhcml0eSI6MTAuOTksInByb2ZpbGVfcGF0aCI6Ii82ZXRsSm56N201MFgzTndHdEg1Zk9PU3pNczIuanBnIiwiY2hhcmFjdGVyIjoixLBsYXlkYSBUYcWfIiwiY3JlZGl0X2lkIjoiNWFlZTIwODNjM2EzNjgwNzc3MDAwYjUzIiwib3JkZXIiOjJ9LHsiYWR1bHQiOmZhbHNlLCJnZW5kZXIiOjIsImlkIjoyMjcxNTQsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiQWN0aW5nIiwibmFtZSI6IkR1cnVsIEJhemFuIiwib3JpZ2luYWxfbmFtZSI6IkR1cnVsIEJhemFuIiwicG9wdWxhcml0eSI6OC40OSwicHJvZmlsZV9wYXRoIjoiL2ZZWGIybWp6dDlRRFlMT1ROM2YwdGpxTUp3Ry5qcGciLCJjaGFyYWN0ZXIiOiJNZXRpbiBFxJ9pbG1leiIsImNyZWRpdF9pZCI6IjVhZWUyMGRjOTI1MTQxMTk2NDAwMTFkMSIsIm9yZGVyIjozfSx7ImFkdWx0IjpmYWxzZSwiZ2VuZGVyIjoxLCJpZCI6MTE0NjczNSwia25vd25fZm9yX2RlcGFydG1lbnQiOiJBY3RpbmciLCJuYW1lIjoixZ5lYm5lbSBEaWxsaWdpbCIsIm9yaWdpbmFsX25hbWUiOiLFnmVibmVtIERpbGxpZ2lsIiwicG9wdWxhcml0eSI6Mi40NzUsInByb2ZpbGVfcGF0aCI6Ii82RUtZQnhMd2FDMTVkdGlXbjBScDZKMFlwRFIuanBnIiwiY2hhcmFjdGVyIjoiTmXFn2UgRcSfaWxtZXoiLCJjcmVkaXRfaWQiOiI1YWVlMjBhNTkyNTE0MTE5OWUwMDBmOTAiLCJvcmRlciI6NH0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MSwiaWQiOjEzODA3OTcsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiQWN0aW5nIiwibmFtZSI6IkfDtnpkZSBDxLHEn2FjxLEiLCJvcmlnaW5hbF9uYW1lIjoiR8O2emRlIEPEscSfYWPEsSIsInBvcHVsYXJpdHkiOjEwLjI5MywicHJvZmlsZV9wYXRoIjoiL2ZZUEV5WXJuRXU2aXdub09HVXNjMHZaaUdKZC5qcGciLCJjaGFyYWN0ZXIiOiJHw7xsIEfDvHJrYW4iLCJjcmVkaXRfaWQiOiI1YWVlMjBjNmMzYTM2ODA3OWIwMDBmM2QiLCJvcmRlciI6NX0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MiwiaWQiOjIwMzY5NzUsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiQWN0aW5nIiwibmFtZSI6IsOHYcSfcsSxIMOHxLF0YW5hayIsIm9yaWdpbmFsX25hbWUiOiLDh2HEn3LEsSDDh8SxdGFuYWsiLCJwb3B1bGFyaXR5IjoxMy4wMTQsInByb2ZpbGVfcGF0aCI6Ii9ucVRMRFh6YTA4aDRpT2w5SnNhNVNtZGlWNnguanBnIiwiY2hhcmFjdGVyIjoiVGVvIiwiY3JlZGl0X2lkIjoiNWFlZTIwNzZjM2EzNjgwNzgzMDAwOTAwIiwib3JkZXIiOjZ9LHsiYWR1bHQiOmZhbHNlLCJnZW5kZXIiOjIsImlkIjoyMDM2OTc5LCJrbm93bl9mb3JfZGVwYXJ0bWVudCI6IkFjdGluZyIsIm5hbWUiOiJCZXJrYXkgVHVsdW1iYWPEsSIsIm9yaWdpbmFsX25hbWUiOiJCZXJrYXkgVHVsdW1iYWPEsSIsInBvcHVsYXJpdHkiOjUuMjMyLCJwcm9maWxlX3BhdGgiOiIvM1VwOHJpT090b1l5Y1l0OGxXdkhYNld0ZnFILmpwZyIsImNoYXJhY3RlciI6IkJhcmJhcm9zIEfDvHJrYW4iLCJjcmVkaXRfaWQiOiI1YWVlMjBhZmMzYTM2ODA3OTIwMDBhOWUiLCJvcmRlciI6N30seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MSwiaWQiOjIwMjI1OTgsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiQWN0aW5nIiwibmFtZSI6IkFsaWNpYSBLYXB1ZGHEnyIsIm9yaWdpbmFsX25hbWUiOiJBbGljaWEgS2FwdWRhxJ8iLCJwb3B1bGFyaXR5IjoyLjA3OCwicHJvZmlsZV9wYXRoIjoiL2R1TFdTWVBKTmgwRUZGODhvZjEzM3dZRkM0WS5qcGciLCJjaGFyYWN0ZXIiOiJBcnp1IEXEn2lsbWV6IiwiY3JlZGl0X2lkIjoiNWFlZTIwYmI5MjUxNDExOTgzMDAxMmM5Iiwib3JkZXIiOjh9LHsiYWR1bHQiOmZhbHNlLCJnZW5kZXIiOjIsImlkIjoyMDM2OTgxLCJrbm93bl9mb3JfZGVwYXJ0bWVudCI6IkFjdGluZyIsIm5hbWUiOiJVbXVyIFlpxJ9pdCBWYW5sxLEiLCJvcmlnaW5hbF9uYW1lIjoiVW11ciBZacSfaXQgVmFubMSxIiwicG9wdWxhcml0eSI6NC4xMjgsInByb2ZpbGVfcGF0aCI6Ii9yVTQ0ektObmNNbTM1ZWdwSDlvd2I3T2lZcnkuanBnIiwiY2hhcmFjdGVyIjoiSGFrYW4gxZ5la2VyIiwiY3JlZGl0X2lkIjoiNWFlZTIwZDAwZTBhMjYxMDI2MDAwZmVjIiwib3JkZXIiOjl9LHsiYWR1bHQiOmZhbHNlLCJnZW5kZXIiOjAsImlkIjoyNjgwNzY1LCJrbm93bl9mb3JfZGVwYXJ0bWVudCI6IkFjdGluZyIsIm5hbWUiOiJMYWwgVGF5cmEgQmFoYXIiLCJvcmlnaW5hbF9uYW1lIjoiTGFsIFRheXJhIEJhaGFyIiwicG9wdWxhcml0eSI6MC44MjMsInByb2ZpbGVfcGF0aCI6bnVsbCwiY2hhcmFjdGVyIjoiw4dpw6dlayBFxJ9pbG1leiIsImNyZWRpdF9pZCI6IjYyYWIzYTlkODc1ZDFhNTI3OWVmMDYyZSIsIm9yZGVyIjoxMH0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MSwiaWQiOjI5NDU4NjMsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiQWN0aW5nIiwibmFtZSI6IsOHacSfZGVtIEF5Z8O8biIsIm9yaWdpbmFsX25hbWUiOiLDh2nEn2RlbSBBeWfDvG4iLCJwb3B1bGFyaXR5IjoyLjA2MiwicHJvZmlsZV9wYXRoIjoiL2VPY2djZ3g1ZlVvRkxxd2JZZDNDZGdPbWN2Ri5qcGciLCJjaGFyYWN0ZXIiOiJTxLFkxLFrYSIsImNyZWRpdF9pZCI6IjYyYWIzYzc2NTU0MWZhMmYyYTMxMDNjYyIsIm9yZGVyIjoxMX0seyJhZHVsdCI6ZmFsc2UsImdlbmRlciI6MSwiaWQiOjIwMzY5ODIsImtub3duX2Zvcl9kZXBhcnRtZW50IjoiQWN0aW5nIiwibmFtZSI6IkFzbMSxIFNhbWF0Iiwib3JpZ2luYWxfbmFtZSI6IkFzbMSxIFNhbWF0IiwicG9wdWxhcml0eSI6NS40NzQsInByb2ZpbGVfcGF0aCI6Ii83TExZaEJvYzY5V3Y4dVA1eFpkZlp3ZVFUWkkuanBnIiwiY2hhcmFjdGVyIjoixZ5pcmluIiwiY3JlZGl0X2lkIjoiNWFlZTIwZTUwZTBhMjYxMDNhMDAwZjQ5Iiwib3JkZXIiOjEyfSx7ImFkdWx0IjpmYWxzZSwiZ2VuZGVyIjoxLCJpZCI6MTMzODE4LCJrbm93bl9mb3JfZGVwYXJ0bWVudCI6IkFjdGluZyIsIm5hbWUiOiJEZXJ5YSBBbGFib3JhIiwib3JpZ2luYWxfbmFtZSI6IkRlcnlhIEFsYWJvcmEiLCJwb3B1bGFyaXR5IjoxMC4wODMsInByb2ZpbGVfcGF0aCI6Ii9sQ2xTM0hkWklLczNOc2U1R0I4U0tpNUZ3UFMuanBnIiwiY2hhcmFjdGVyIjoiQ2FoaWRlIFBhcnMiLCJjcmVkaXRfaWQiOiI1YWVlMjA4ZDBlMGEyNjEwNDcwMDBlYzQiLCJvcmRlciI6MTN9XSwiY3JldyI6W10sImlkIjo3Mjg5M30= + recorded_at: Tue, 03 Sep 2024 23:34:54 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/listeners/disk_listener_spec.rb b/spec/listeners/disk_listener_spec.rb new file mode 100644 index 00000000..74727d88 --- /dev/null +++ b/spec/listeners/disk_listener_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe DiskListener do + subject(:listener) { described_class.new(**args) } + + let(:args) { {} } + + include ActionView::Helpers::UrlHelper + delegate :job_url, to: 'Rails.application.routes.url_helpers' + + describe '#disk_loaded' do + subject(:disk_loaded) { listener.disk_loaded(disk) } + + before do + allow(listener).to receive(:cable_ready).and_return(cable_ready) + allow(channel).to receive(:reload) + allow(channel).to receive(:redirect_to) + allow(cable_ready).to receive(:broadcast) + end + + let(:cable_ready) { instance_double(CableReady::Channels, '[]': channel) } + let(:channel) { CableReady::Channel.new('broadcast') } + + context 'when disk has auto load off' do + let(:disk) { create(:disk) } + + it 'broadcast a page reload' do + expect(disk_loaded).to be_nil + expect(channel).to have_received(:reload) + expect(cable_ready).to have_received(:broadcast) + end + end + + context 'when the disk loaded is not a movie & auto start is true' do + let(:disk) { create(:disk, video:) } + let(:video) { create(:tv, auto_start: true) } + + it 'broadcast a page reload' do + expect(disk_loaded).to be_nil + expect(channel).to have_received(:reload) + expect(cable_ready).to have_received(:broadcast) + end + end + + context 'when disk has auto start set to true' do + let(:disk) { create(:disk, video:, disk_titles:) } + let(:video) { create(:movie, movie_runtime: 20, auto_start: true) } + let(:disk_titles) { create_list(:disk_title, 2, duration: 20) } + + it 'broadcast a redirect to the new job' do + expect(disk_loaded).to be_nil + expect(channel).not_to have_received(:reload) + expect(channel).to have_received(:redirect_to).with(url: job_url(Job.newest.first)) + end + end + end +end diff --git a/spec/listeners/the_movie_db/video_listener_spec.rb b/spec/listeners/the_movie_db/video_listener_spec.rb index e0faa98a..3ba54257 100644 --- a/spec/listeners/the_movie_db/video_listener_spec.rb +++ b/spec/listeners/the_movie_db/video_listener_spec.rb @@ -23,6 +23,7 @@ 'movie_runtime' => nil, 'the_movie_db_id' => 4629, 'popularity' => nil, + 'auto_start' => false, 'overview' => 'The story of Stargate SG-1 begins about a year after ' \ 'the events of the feature film, when the United States government ' \ 'learns that an ancient alien device called the Stargate can access ' \ diff --git a/spec/models/movie_spec.rb b/spec/models/movie_spec.rb index 80b668c1..0705f02b 100644 --- a/spec/models/movie_spec.rb +++ b/spec/models/movie_spec.rb @@ -5,6 +5,7 @@ # Table name: videos # # id :integer not null, primary key +# auto_start :boolean default(FALSE), not null # backdrop_path :string # episode_distribution_runtime :string # episode_first_air_date :date diff --git a/spec/models/tv_spec.rb b/spec/models/tv_spec.rb index cd0bfaf7..6e466120 100644 --- a/spec/models/tv_spec.rb +++ b/spec/models/tv_spec.rb @@ -5,6 +5,7 @@ # Table name: videos # # id :integer not null, primary key +# auto_start :boolean default(FALSE), not null # backdrop_path :string # episode_distribution_runtime :string # episode_first_air_date :date diff --git a/spec/models/video_spec.rb b/spec/models/video_spec.rb index 2cc5de92..3f1d1187 100644 --- a/spec/models/video_spec.rb +++ b/spec/models/video_spec.rb @@ -5,6 +5,7 @@ # Table name: videos # # id :integer not null, primary key +# auto_start :boolean default(FALSE), not null # backdrop_path :string # episode_distribution_runtime :string # episode_first_air_date :date