Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions lib/recording/create_recording_job.ex
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
defmodule Onvif.Recording.CreateRecordingJob do
import SweetXml
import XmlBuilder

require Logger

alias Onvif.Recording.Schemas.JobConfiguration

def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/CreateRecordingJob"

def request(device, args) do
Onvif.Recording.request(device, args, __MODULE__)
end

def request_body(recording_token, priority \\ "0", mode \\ "Active") do
def request_body(%JobConfiguration{} = config) do
element(:"s:Body", [
element(:"trc:CreateRecordingJob", [
element(:"trc:JobConfiguration", [
element(:"tt:RecordingToken", recording_token),
element(:"tt:Mode", mode),
element(:"tt:Priority", priority)
])
element(:"trc:JobConfiguration", JobConfiguration.to_xml(config))
])
])
end
Expand Down
166 changes: 166 additions & 0 deletions lib/recording/schemas/job_configuration.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
defmodule Onvif.Recording.Schemas.JobConfiguration do
@moduledoc """
Schema describing a recording job configuration.
"""

use Ecto.Schema

import Ecto.Changeset
import SweetXml
import XmlBuilder

@primary_key false
@derive Jason.Encoder
embedded_schema do
field(:recording_token, :string)
field(:mode, Ecto.Enum, values: [idle: "Idle", active: "Active"])
field(:priority, :integer)

embeds_one :source, Source, primary_key: false, on_replace: :update do
@derive Jason.Encoder
field(:auto_create_receiver, :boolean)

embeds_one :source_token, SourceToken, primary_key: false, on_replace: :update do
@derive Jason.Encoder
field(:token, :string)
end

embeds_many :tracks, Tracks, primary_key: false, on_replace: :delete do
@derive Jason.Encoder
field(:source_tag, :string)
field(:destination, :string)
end
end
end

def parse([]), do: nil
def parse(nil), do: nil

def parse(doc) do
xmap(
doc,
recording_token: ~x"./tt:RecordingToken/text()"so,
mode: ~x"./tt:Mode/text()"so,
priority: ~x"./tt:Priority/text()"so,
source: ~x"./tt:Source"eo |> transform_by(&parse_source/1)
)
end

def to_xml(%__MODULE__{} = job_configuration) do
field_to_xml([], "tt:RecordingToken", job_configuration.recording_token)
|> field_to_xml(
"tt:Mode",
Keyword.get(Ecto.Enum.mappings(__MODULE__, :mode), job_configuration.mode)
)
|> field_to_xml("tt:Priority", job_configuration.priority)
|> source_to_xml(job_configuration.source)
end

def to_struct(parsed) do
%__MODULE__{}
|> changeset(parsed)
|> apply_action(:validate)
end

def changeset(job_configuration, attrs) do
job_configuration
|> cast(attrs, [:recording_token, :mode, :priority])
|> cast_embed(:source, with: &source_changeset/2)
end

defp parse_source([]), do: nil
defp parse_source(nil), do: nil

defp parse_source(doc) do
xmap(
doc,
source_token: ~x"./tt:SourceToken"eo |> transform_by(&parse_source_token/1),
auto_create_receiver: ~x"./tt:AutoCreateReceiver/text()"so,
tracks: ~x"./tt:Tracks"elo |> transform_by(&parse_track/1)
)
end

defp parse_source_token([]), do: nil
defp parse_source_token(nil), do: nil

defp parse_source_token(doc) do
xmap(
doc,
token: ~x"./tt:Token/text()"so
)
end

defp parse_track([]), do: nil
defp parse_track(nil), do: nil

defp parse_track(docs) do
Enum.map(docs, fn doc ->
xmap(
doc,
source_tag: ~x"./tt:SourceTag/text()"so,
destination: ~x"./tt:Destination/text()"so
)
end)
end

defp source_changeset(schema, params) do
schema
|> cast(params, [:auto_create_receiver])
|> cast_embed(:source_token, with: &source_token_changeset/2)
|> cast_embed(:tracks, with: &track_changeset/2)
end

defp source_token_changeset(schema, params) do
schema
|> cast(params, [:token])
|> validate_required([:token])
end

defp track_changeset(schema, params) do
schema
|> cast(params, [:source_tag, :destination])
end

defp source_to_xml(builder, nil), do: builder

defp source_to_xml(builder, source) do
source =
element(
"tt:Source",
field_to_xml([], "tt:AutoCreateReceiver", source.auto_create_receiver)
|> source_token_to_xml(source.source_token)
|> tracks_to_xml(source.tracks)
)

[source | builder]
end

defp source_token_to_xml(builder, nil), do: builder

defp source_token_to_xml(builder, source_token) do
source_token = element("tt:SourceToken", field_to_xml([], "tt:Token", source_token.token))
[source_token | builder]
end

defp tracks_to_xml(builder, nil), do: builder
defp tracks_to_xml(builder, []), do: builder

defp tracks_to_xml(builder, tracks) do
tracks =
Enum.map(tracks, fn track ->
element(
"tt:Tracks",
field_to_xml([], "tt:SourceTag", track.source_tag)
|> field_to_xml("tt:Destination", track.destination)
)
end)

[tracks | builder]
end

defp field_to_xml(builder, _field, nil), do: builder

defp field_to_xml(builder, key, value) do
[element(key, value) | builder]
end
end
111 changes: 6 additions & 105 deletions lib/recording/schemas/recording_job.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ defmodule Onvif.Recording.Schemas.RecordingJob do
import Ecto.Changeset
import SweetXml

alias Onvif.Recording.Schemas.JobConfiguration

@required [:job_token]
@optional []

Expand All @@ -17,28 +19,7 @@ defmodule Onvif.Recording.Schemas.RecordingJob do
embedded_schema do
field(:job_token, :string)

embeds_one :job_configuration, JobConfiguration, primary_key: false, on_replace: :update do
@derive Jason.Encoder
field(:recording_token, :string)
field(:mode, :string)
field(:priority, :string)

embeds_one :source, Source, primary_key: false, on_replace: :update do
@derive Jason.Encoder
field(:auto_create_receiver, :boolean)

embeds_one :source_token, SourceToken, primary_key: false, on_replace: :update do
@derive Jason.Encoder
field(:token, :string)
end

embeds_many :tracks, Tracks, primary_key: false, on_replace: :delete do
@derive Jason.Encoder
field(:source_tag, :string)
field(:destination, :string)
end
end
end
embeds_one(:job_configuration, JobConfiguration)
end

def parse(nil), do: nil
Expand All @@ -48,72 +29,17 @@ defmodule Onvif.Recording.Schemas.RecordingJob do
xmap(
doc,
job_token: ~x"./tt:JobToken/text()"so,
job_configuration: ~x"./tt:JobConfiguration"eo |> transform_by(&parse_job_configuration/1)
job_configuration: ~x"./tt:JobConfiguration"eo |> transform_by(&JobConfiguration.parse/1)
)
end

def parse_job_configuration([]), do: nil
def parse_job_configuration(nil), do: nil

def parse_job_configuration(doc) do
xmap(
doc,
recording_token: ~x"./tt:RecordingToken/text()"so,
mode: ~x"./tt:Mode/text()"so,
priority: ~x"./tt:Priority/text()"so,
source: ~x"./tt:Source"eo |> transform_by(&parse_source/1)
)
end

def parse_source([]), do: nil
def parse_source(nil), do: nil

def parse_source(doc) do
xmap(
doc,
source_token: ~x"./tt:SourceToken"eo |> transform_by(&parse_source_token/1),
auto_create_receiver: ~x"./tt:AutoCreateReceiver/text()"so,
tracks: ~x"./tt:Tracks"elo |> transform_by(&parse_track/1)
)
end

def parse_source_token([]), do: nil
def parse_source_token(nil), do: nil

def parse_source_token(doc) do
xmap(
doc,
token: ~x"./tt:Token/text()"so
)
end

def parse_track([]), do: nil
def parse_track(nil), do: nil

def parse_track(docs) do
Enum.map(docs, fn doc ->
xmap(
doc,
source_tag: ~x"./tt:SourceTag/text()"so,
destination: ~x"./tt:Destination/text()"so
)
end)
end

def to_struct(parsed) do
%__MODULE__{}
|> changeset(parsed)
|> apply_action(:validate)
end

@spec to_json(__MODULE__.t()) ::
{:error,
%{
:__exception__ => any,
:__struct__ => Jason.EncodeError | Protocol.UndefinedError,
optional(atom) => any
}}
| {:ok, binary}
@spec to_json(__MODULE__.t()) :: {:error, Jason.EncodeError.t() | Exception.t()} | {:ok, binary}
def to_json(%__MODULE__{} = schema) do
Jason.encode(schema)
end
Expand All @@ -122,31 +48,6 @@ defmodule Onvif.Recording.Schemas.RecordingJob do
module
|> cast(attrs, @required ++ @optional)
|> validate_required(@required)
|> cast_embed(:job_configuration, with: &job_configuration_changeset/2)
end

def job_configuration_changeset(schema, params) do
schema
|> cast(params, [:recording_token, :mode, :priority])
|> validate_required([:recording_token, :mode, :priority])
|> cast_embed(:source, with: &source_changeset/2)
end

def source_changeset(schema, params) do
schema
|> cast(params, [:auto_create_receiver])
|> cast_embed(:source_token, with: &source_token_changeset/2)
|> cast_embed(:tracks, with: &track_changeset/2)
end

def source_token_changeset(schema, params) do
schema
|> cast(params, [:token])
|> validate_required([:token])
end

def track_changeset(schema, params) do
schema
|> cast(params, [:source_tag, :destination])
|> cast_embed(:job_configuration, with: &JobConfiguration.changeset/2)
end
end
12 changes: 7 additions & 5 deletions test/recording/create_recording_job_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ defmodule Onvif.Recording.CreateRecordingJobTest do

@moduletag capture_log: true

alias Onvif.Recording.Schemas.JobConfiguration

describe "CreateRecordingJob/2" do
test "create a recording" do
xml_response = File.read!("test/recording/fixture/create_recording_job_success.xml")
Expand All @@ -14,11 +16,11 @@ defmodule Onvif.Recording.CreateRecordingJobTest do
end)

{:ok, response} =
Onvif.Recording.CreateRecordingJob.request(device, [
"SD_DISK_20241120_211729_9C896594",
"9",
"Active"
])
Onvif.Recording.CreateRecordingJob.request(device, %JobConfiguration{
recording_token: "SD_DISK_20241120_211729_9C896594",
priority: 9,
mode: :active
})

assert response == "SD_DISK_20241120_211729_9C896594"
end
Expand Down