Awlexus

Awlexus

Streaming xml file over ftp fails if I don't convert it into a list first

I’m trying to stream an xml file over ftp with Stream.resource/3 and SweetXml.stream_tags!/3, but I’m running into a strange issue where sweet_xml raises an error, before chunk arrives, unless I call Enum.to_list/1.

# Function definitions

def stream_file!(client) do
  Stream.resource(fn -> start_file_stream(client) end, &next_file_chunk/1, &stop_file_stream/1)
end

defp start_file_stream(client) do
  Logger.debug("Requesting to stream file")

  with :ok <- :ftp.recv_chunk_start(client.conn, to_charlist(client.credentials.file_path)) do
    client
  end
end

defp next_file_chunk(%{conn: conn} = client) do
  case :ftp.recv_chunk(conn) do
    {:ok, binary} ->
      Logger.debug("Stream file chunk #{byte_size(binary)} bytes)")
      {[binary], client}

    :ok ->
      Logger.debug("Transfer completed successfully")
      {:halt, client}

    error ->
      Logger.error(
        credentials_id: client.credentials.id,
        message: "Unable to stream file",
        reason: error
      )

      {:halt, error}
  end
end

defp next_file_chunk(error), do: {:halt, error}
defp stop_file_stream(_), do: []

# Code that is executed

# conn is the pid from :ftp.open/2
# Credentials holds infos about the file and the credentials for the ftp server
client = %{conn: conn, credentials: credentials}

client
|> stream_file!()
# For debugging
|> Stream.each(&IO.inspect(&1, label: :chunk))
# |> Enum.to_list()
|> SweetXml.stream_tags!(:Mitglied, discard: [:Mitglied])
|> Stream.map(fn {:Mitglied, elem} -> 
  # parse data here
end)
|> Stream.run()

Calling this code produces the following output. If I uncomment |> Enum.to_list() it will work fine and print all the chunks as they come. Any idea what might cause this?

[debug] Requesting to stream file
[error] 3917- fatal: :expected_element_start_tag

** (SweetXml.XmerlFatal) :expected_element_start_tag
    (sweet_xml 0.7.3) lib/sweet_xml.ex:539: anonymous fn/1 in SweetXml.stream!/2
    (elixir 1.14.4) lib/stream.ex:1619: Stream.do_resource/5
    (elixir 1.14.4) lib/stream.ex:1813: Enumerable.Stream.do_each/4
    (elixir 1.14.4) lib/stream.ex:689: Stream.run/1

For reference, I have a separate implementation for streaming files over sftp with sftp_client, which works perfectly. I’ve looked into their implementation, but couldn’t find any meaningful difference.

Marked As Solved

Awlexus

Awlexus

Then I’ll just take this advice and just admit defeat. Enum.to_list may stay here for now

Also Liked

D4no0

D4no0

Yeah sftp erlang client works pretty much flawlessly, the standard is sound and almost all servers use pretty much the same version (v3 sftp, v2 ssh if I remember well), so I would recommend to use that instead of calling an external process, because there is always the risk in a long-running system that you will have some kind process leaks at some point.

Awlexus

Awlexus

The erlang ftp module does not allow you to set the chunksize (or at least I couldn’t find it in their docs), so I assume it’s sftp

dimitarvp

dimitarvp

No clear-and-cut answer but I would very likely opt for having N background rclone processes waiting (a pool for Oban workers for example) and then enforce uniqueness via Oban’s mechanism so e.g. you can’t write to the same file from 2 or more workers. That’s trivially achievable.

The tricky part is of course if somebody wants the file upload to be synchronous but IIRC Oban offers that as well? (I think you can send a job and wait on it?)

BTW when it comes to sFTP in particular I think Erlang is slightly better equipped but don’t quote me on this, I only used a library for it once and it was a few years ago. I’d still try work with a normal library and only reach for an external tool if things don’t work stably – as it happened with OP.

But you also said you don’t want to tie your application’s logic to the transport protocol. Hm, in that case I’d probably choose ex_aws_s3 because you can still use it not only for Amazon S3 like Cloudflare’s B2 but also for self-hosted software like MinIO.

Where Next?

Popular in Questions Top

dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
New
dokuzbir
Hello, I am trying to convert my lists to string without losing brackets.For start i have 3 map. They look like these buyer = %{ id: ...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

Other popular topics Top

lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

We're in Beta

About us Mission Statement