[Enhancement] Auto-update yt-dlp (#589)

* Added a command for updating yt-dlp

* Added a yt-dlp update worker to run daily

* Added a new file that runs post-boot when the app is ready to serve requests; put yt-dlp updater in there

* Updated config to expose the current env globally; updated startup tasks to not run in test env

* Removes unneeded test code
This commit is contained in:
Kieran
2025-01-27 11:33:38 -08:00
committed by GitHub
parent 62214b80a6
commit 6ead29182d
14 changed files with 202 additions and 26 deletions
+9 -11
View File
@@ -9,8 +9,12 @@ defmodule Pinchflat.Application do
@impl true
def start(_type, _args) do
check_and_update_timezone()
attach_oban_telemetry()
Logger.add_handlers(:pinchflat)
children = [
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
[
Pinchflat.PromEx,
PinchflatWeb.Telemetry,
Pinchflat.Repo,
@@ -24,17 +28,11 @@ defmodule Pinchflat.Application do
{Finch, name: Pinchflat.Finch},
# Start a worker by calling: Pinchflat.Worker.start_link(arg)
# {Pinchflat.Worker, arg},
# Start to serve requests, typically the last entry
PinchflatWeb.Endpoint
# Start to serve requests, typically the last entry (except for the post-boot tasks)
PinchflatWeb.Endpoint,
Pinchflat.Boot.PostBootStartupTasks
]
attach_oban_telemetry()
Logger.add_handlers(:pinchflat)
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Pinchflat.Supervisor]
Supervisor.start_link(children, opts)
|> Supervisor.start_link(strategy: :one_for_one, name: Pinchflat.Supervisor)
end
# Tell Phoenix to update the endpoint configuration
@@ -0,0 +1,46 @@
defmodule Pinchflat.Boot.PostBootStartupTasks do
@moduledoc """
This module is responsible for running startup tasks on app boot
AFTER all other boot steps have taken place and the app is ready to serve requests.
It's a GenServer because that plays REALLY nicely with the existing
Phoenix supervision tree.
"""
alias Pinchflat.YtDlp.UpdateWorker, as: YtDlpUpdateWorker
# restart: :temporary means that this process will never be restarted (ie: will run once and then die)
use GenServer, restart: :temporary
import Ecto.Query, warn: false
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, %{env: Application.get_env(:pinchflat, :env)}, opts)
end
@doc """
Runs post-boot application startup tasks.
Any code defined here will run every time the application starts. You must
make sure that the code is idempotent and safe to run multiple times.
This is a good place to set up default settings, create initial records, stuff like that.
Should be fast - anything with the potential to be slow should be kicked off as a job instead.
"""
@impl true
def init(%{env: :test} = state) do
# Do nothing _as part of the app bootup process_.
# Since bootup calls `start_link` and that's where the `env` state is injected,
# you can still call `.init()` manually to run these tasks for testing purposes
{:ok, state}
end
def init(state) do
update_yt_dlp()
{:ok, state}
end
defp update_yt_dlp do
YtDlpUpdateWorker.kickoff()
end
end
+9 -2
View File
@@ -1,7 +1,7 @@
defmodule Pinchflat.Boot.PostJobStartupTasks do
@moduledoc """
This module is responsible for running startup tasks on app boot
AFTER the job runner has initiallized.
AFTER the job runner has initialized.
It's a GenServer because that plays REALLY nicely with the existing
Phoenix supervision tree.
@@ -12,7 +12,7 @@ defmodule Pinchflat.Boot.PostJobStartupTasks do
import Ecto.Query, warn: false
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, %{}, opts)
GenServer.start_link(__MODULE__, %{env: Application.get_env(:pinchflat, :env)}, opts)
end
@doc """
@@ -25,6 +25,13 @@ defmodule Pinchflat.Boot.PostJobStartupTasks do
Should be fast - anything with the potential to be slow should be kicked off as a job instead.
"""
@impl true
def init(%{env: :test} = state) do
# Do nothing _as part of the app bootup process_.
# Since bootup calls `start_link` and that's where the `env` state is injected,
# you can still call `.init()` manually to run these tasks for testing purposes
{:ok, state}
end
def init(state) do
# Nothing at the moment!
+8 -1
View File
@@ -19,7 +19,7 @@ defmodule Pinchflat.Boot.PreJobStartupTasks do
alias Pinchflat.Lifecycle.UserScripts.CommandRunner, as: UserScriptRunner
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, %{}, opts)
GenServer.start_link(__MODULE__, %{env: Application.get_env(:pinchflat, :env)}, opts)
end
@doc """
@@ -32,6 +32,13 @@ defmodule Pinchflat.Boot.PreJobStartupTasks do
Should be fast - anything with the potential to be slow should be kicked off as a job instead.
"""
@impl true
def init(%{env: :test} = state) do
# Do nothing _as part of the app bootup process_.
# Since bootup calls `start_link` and that's where the `env` state is injected,
# you can still call `.init()` manually to run these tasks for testing purposes
{:ok, state}
end
def init(state) do
ensure_tmpfile_directory()
reset_executing_jobs()
@@ -14,7 +14,7 @@ defmodule Pinchflat.Profiles.MediaProfileDeletionWorker do
Starts the profile deletion worker. Does not attach it to a task like `kickoff_with_task/2`
since deletion also cancels all tasks for the profile
Returns {:ok, %Task{}} | {:error, %Ecto.Changeset{}}
Returns {:ok, %Oban.Job{}} | {:error, %Ecto.Changeset{}}
"""
def kickoff(profile, job_args \\ %{}, job_opts \\ []) do
%{id: profile.id}
+18
View File
@@ -76,6 +76,24 @@ defmodule Pinchflat.YtDlp.CommandRunner do
end
end
@doc """
Updates yt-dlp to the latest version
Returns {:ok, binary()} | {:error, binary()}
"""
@impl YtDlpCommandRunner
def update do
command = backend_executable()
case CliUtils.wrap_cmd(command, ["--update"]) do
{output, 0} ->
{:ok, String.trim(output)}
{output, _} ->
{:error, output}
end
end
defp generate_output_filepath(addl_opts) do
case Keyword.get(addl_opts, :output_filepath) do
nil -> FSUtils.generate_metadata_tmpfile(:json)
+44
View File
@@ -0,0 +1,44 @@
defmodule Pinchflat.YtDlp.UpdateWorker do
@moduledoc false
use Oban.Worker,
queue: :local_data,
tags: ["local_data"]
require Logger
alias __MODULE__
alias Pinchflat.Settings
@doc """
Starts the yt-dlp update worker. Does not attach it to a task like `kickoff_with_task/2`
Returns {:ok, %Oban.Job{}} | {:error, %Ecto.Changeset{}}
"""
def kickoff do
Oban.insert(UpdateWorker.new(%{}))
end
@doc """
Updates yt-dlp and saves the version to the settings.
This worker is scheduled to run via the Oban Cron plugin as well as on app boot.
Returns :ok
"""
@impl Oban.Worker
def perform(%Oban.Job{}) do
Logger.info("Updating yt-dlp")
yt_dlp_runner().update()
{:ok, yt_dlp_version} = yt_dlp_runner().version()
Settings.set(yt_dlp_version: yt_dlp_version)
:ok
end
defp yt_dlp_runner do
Application.get_env(:pinchflat, :yt_dlp_runner)
end
end
@@ -9,4 +9,5 @@ defmodule Pinchflat.YtDlp.YtDlpCommandRunner do
@callback run(binary(), atom(), keyword(), binary()) :: {:ok, binary()} | {:error, binary(), integer()}
@callback run(binary(), atom(), keyword(), binary(), keyword()) :: {:ok, binary()} | {:error, binary(), integer()}
@callback version() :: {:ok, binary()} | {:error, binary()}
@callback update() :: {:ok, binary()} | {:error, binary()}
end
+1 -1
View File
@@ -20,7 +20,7 @@ defmodule PinchflatWeb.Endpoint do
plug Plug.Static,
at: "/",
from: :pinchflat,
gzip: Mix.env() == :prod,
gzip: Application.compile_env(:pinchflat, :env) == :prod,
only: PinchflatWeb.static_paths()
# Code reloading can be explicitly enabled under the