Skip to content

Commit

Permalink
Improved time tracking for uploading files and ripping movies (#470)
Browse files Browse the repository at this point in the history
  • Loading branch information
brand-it authored Oct 10, 2024
1 parent f3da076 commit ae27d3e
Show file tree
Hide file tree
Showing 21 changed files with 721 additions and 30 deletions.
12 changes: 12 additions & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ body {
margin: auto;
}

.overflow-hidden {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.overflow-hidden:hover {
white-space: normal;
overflow: visible;
text-overflow: clip;
}

// Console for basic shell output
.console-content {
display: flex;
Expand Down
28 changes: 18 additions & 10 deletions app/components/progress_bar_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
<div>
<div class="progress mb-2">
<div class="progress-bar progress-bar-striped <%= 'progress-bar-animated' if completed < 100 %> bg-<%= status %>" role="progressbar" aria-valuenow="<%= completed %>" aria-valuemin="0" aria-valuemax="100" style="width: <%= completed %>%">
<div class="row">
<div class="<%= show_percentage ? 'col-9' : 'col-12' %>">
<div class="progress mb-2">
<div class="progress-bar progress-bar-striped <%= 'progress-bar-animated' if completed < 100 %> bg-<%= status %>" role="progressbar" aria-valuenow="<%= completed %>" aria-valuemin="0" aria-valuemax="100" style="width: <%= completed %>%">
</div>
</div>
</div>
</div>
<div>
<%= message.presence %>
<% if show_percentage %>
...
<%= number_to_percentage completed, precision: 2 %>
<% if eta.present? %>
(<%= eta %>)
<% end %>
<div class="mb-2 col-3">
<%= number_to_percentage completed, precision: 2 %>
</div>
<% end %>
</div>
<div class="overflow-hidden">
<%= message.presence&.html_safe %>
</div>
<% if eta.present? %>
<div>ETA: <%= eta %></div>
<% end %>
<% if progress.present? %>
<div>Progress: <%= progress %></div>
<% end %>
</div>
1 change: 1 addition & 0 deletions app/components/progress_bar_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ class ProgressBarComponent < ViewComponent::Base
option :message, optional: true
option :show_percentage, default: -> { true }
option :eta, optional: true
option :progress, optional: true
end
2 changes: 1 addition & 1 deletion app/components/rip_process_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
completed: job.metadata['completed'],
status: :info,
message: job.metadata['title'],
eta:
eta: job.metadata['eta']
)
)
%>
Expand Down
13 changes: 9 additions & 4 deletions app/components/upload_process_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@
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} ##{blob.id}",
eta: (eta(video_blob_job, blob) if video_blob_job)
completed: video_blob_job&.completed,
message: video_blob_job ? "Uploading #{link_to_video(blob)}" : "Pending #{link_to_video(blob)} ##{blob.id}",
eta: video_blob_job&.metadata&.fetch('eta', nil),
progress: [
video_blob_job&.metadata&.fetch('progress', nil),
video_blob_job&.metadata&.fetch('rate', nil)
].compact_blank.join(' ')
)
)
%>
<hr>
<% end %>

<% uploaded_recently_video_blobs.each do |blob| %>
Expand All @@ -29,7 +34,7 @@
show_percentage: false,
status: :success,
completed: 100,
message: "Uploaded #{blob.title}"
message: "Uploaded #{link_to_video(blob)}"
)
)
%>
Expand Down
8 changes: 8 additions & 0 deletions app/components/upload_process_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ def uploaded_recently_video_blobs
.limit(3)
end

def link_to_video(blob)
return link_to(blob.title, movie_path(blob.video)) if blob.video.is_a?(Movie)

return link_to(blob.title, tv_season_path(blob.video, blob.episode.season)) if blob.episode && blob.video.is_a?(Tv)

blob.title
end

def job_active?
job&.active?
end
Expand Down
19 changes: 18 additions & 1 deletion app/listeners/mkv_progress_listener.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,12 @@ def mkv_started(cmd)
def mkv_raw_line(mkv_message)
case mkv_message
when MkvParser::PRGV
job.completed = percentage(mkv_message.current, mkv_message.pmax)
tracker(mkv_message.pmax)
increment_progress_bar(mkv_message.current)
job.completed = tracker.percentage_component.percentage_with_precision
job.metadata['eta'] = tracker.time_component.estimated_without_label
when MkvParser::PRGT, MkvParser::PRGC
@tracker = nil
job.title = [video_blob&.title, mkv_message.name].compact_blank.join("\n")
when MkvParser::MSG
job.add_message(mkv_message.message)
Expand All @@ -74,6 +78,19 @@ def mkv_raw_line(mkv_message)

private

def tracker(total = nil)
@tracker = nil if total && @tracker&.total != total
@tracker ||= ProgressTracker::Base.new(total:)
end

def increment_progress_bar(progress)
return if tracker.finished?

tracker.progress = progress
rescue ProgressBar::InvalidProgressError
tracker&.finish
end

def reload_page!
cable_ready[BroadcastChannel.channel_name].reload
cable_ready.broadcast
Expand Down
21 changes: 15 additions & 6 deletions app/listeners/upload_progress_listener.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class UploadProgressListener

attr_reader :completed

def upload_progress(total_uploaded:)
job.completed = total_uploaded
def upload_progress(tracker:)
update_meta_from_tracker(tracker)
return if next_update.future?

job.save!
Expand All @@ -25,15 +25,15 @@ def upload_ready
upload_started
end

def upload_started
job.completed = 0
def upload_started(tracker: nil)
update_meta_from_tracker(tracker)
job.metadata['video_blob_id'] = video_blob.id
job.save!
update_component
end

def upload_finished
job.completed = video_blob.byte_size
def upload_finished(tracker:)
update_meta_from_tracker(tracker)
job.save!
video_blob.update!(uploadable: false, uploaded_on: Time.current)
update_component
Expand All @@ -46,6 +46,15 @@ def upload_error(exception)

private

def update_meta_from_tracker(tracker)
return if tracker.nil?

job.completed = tracker.percentage_component.percentage_with_precision
job.metadata['eta'] = tracker.time_component.estimated_without_label
job.metadata['rate'] = "#{tracker.rate_component.rate_of_change_with_precision} KB/sec"
job.metadata['progress'] = "#{tracker.progress} KB"
end

def update_component
component = UploadProcessComponent.new
cable_ready[BroadcastChannel.channel_name].morph \
Expand Down
29 changes: 24 additions & 5 deletions app/services/ftp/upload_mkv_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ def call
try_to { ftp_upload_file }
tmp_destroy_folder
mark_as_uploaded!
broadcast(:upload_finished)
broadcast(:upload_finished, tracker:)
rescue StandardError => e
broadcast(:upload_error, e)
raise e
end

private

attr_reader :tracker

def ftp_destroy_if_file_exists
ftp.delete(video_blob.plex_path)
rescue Net::FTPPermError => e
Expand All @@ -41,14 +43,31 @@ def ftp_create_directory
end

def ftp_upload_file
broadcast(:upload_started)
total_uploaded = 0
@tracker = ProgressTracker::Base.new(
total: bytes_to_kilobyte(file.size),
rate_scale: ->(rate) { rate / 1024 }
)
broadcast(:upload_started, tracker:)
ftp.putbinaryfile(file, video_blob.plex_path) do |chunk|
total_uploaded += chunk.size
broadcast(:upload_progress, total_uploaded:)
increment_progress_bar(chunk.size)
broadcast(:upload_progress, tracker:)
end
end

def increment_progress_bar(increment)
return if tracker.finished?

@total_bytes ||= 0
@total_bytes += increment
tracker.progress = bytes_to_kilobyte(@total_bytes)
rescue ProgressBar::InvalidProgressError
tracker&.finish
end

def bytes_to_kilobyte(bites)
bites / 1000
end

def mark_as_uploaded!
video_blob.update!(uploaded_on: Time.current, uploadable: false)
end
Expand Down
1 change: 1 addition & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Bundler.require(*Rails.groups)
require 'view_component/engine'
require 'sys/filesystem'
require './lib/progress_tracker/base'

module PlexRipper
VERSION = File.read(File.expand_path('./current_version.txt')).gsub('v', '').strip
Expand Down
2 changes: 1 addition & 1 deletion current_version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v5.4.1
v5.5.0
132 changes: 132 additions & 0 deletions lib/progress_tracker/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# frozen_string_literal: true

require_relative 'time'
require_relative 'timer'
require_relative 'progress'
require_relative 'projector'
require_relative 'projectors/smoothed_average'
require_relative 'component/rate'
require_relative 'component/time'
require_relative 'component/percentage'

module ProgressTracker
class Base
extend Forwardable

def_delegators :progressable,
:progress,
:total
def initialize(options = {})
options[:projector] ||= {}

self.autostart = options.fetch(:autostart, true)
self.autofinish = options.fetch(:autofinish, true)
self.finished = false

self.timer = Timer.new(options)
self.projector = Projector
.from_type(options[:projector][:type])
.new(options[:projector])
self.progressable = Progress.new(options)

options = options.merge(progress: progressable,
projector:,
timer:)

self.percentage_component = Components::Percentage.new(options)
self.rate_component = Components::Rate.new(options)
self.time_component = Components::Time.new(options)

start at: options[:starting_at] if autostart
end

def start(options = {})
timer.start
update_progress(:start, options)
end

def finish
return if finished?

output.with_refresh do
self.finished = true
progressable.finish
timer.stop
end
end

def pause
output.with_refresh { timer.pause } unless paused?
end

def stop
output.with_refresh { timer.stop } unless stopped?
end

def resume
output.with_refresh { timer.resume } if stopped?
end

def reset
output.with_refresh do
self.finished = false
progressable.reset
projector.reset
timer.reset
end
end

def stopped?
timer.stopped? || finished?
end

alias paused? stopped?

def finished?
finished || (autofinish && progressable.finished?)
end

def started? # rubocop:disable Rails/Delegate
timer.started?
end

def decrement
update_progress(:decrement)
end

def increment
update_progress(:increment)
end

def progress=(new_progress)
update_progress(:progress=, new_progress)
end

def total=(new_total)
update_progress(:total=, new_total)
end

def inspect
"#<ProgressBar:#{progress}/#{total || 'unknown'}>"
end

attr_accessor :percentage_component,
:rate_component,
:time_component

protected

attr_accessor :autofinish,
:autostart,
:finished,
:progressable,
:projector,
:timer

def update_progress(*)
progressable.__send__(*)
projector.__send__(*)
timer.stop if finished?
end
end
end
Loading

0 comments on commit ae27d3e

Please sign in to comment.