defmodule OpencadcVospaceClient do @moduledoc """ VOSpace client to upload a stream of data. Useful for transferring from one location to a VOSpace. """ @doc """ Upload a file to a VOSpace `sourceStream`: The source of data to PUT to VOSpace. `destinationURI`: VOS URI to put to. `cookieCredential`: Cookie value for auth. """ def upload(sourceStream, destinationURI, cookieCredential) do OpencadcRegistryClient.lookupServiceURL("ivo://cadc.nrc.ca/vospace", "ivo://ivoa.net/std/VOSpace/v2.0#nodes", "vs:ParamHTTP") <> URI.parse(destinationURI).path |> HTTPoison.put!( toXML(destinationURI), [{"Content-Type", "text/xml"}, {"Cookie", "CADC_SSO=\"" <> cookieCredential <> "\""}], [{:follow_redirect, true}] ) |> write(sourceStream, destinationURI, cookieCredential) end defp write(%HTTPoison.Response{status_code: 200}, sourceStream, destinationURI, cookieCredential) do transferServiceURL = OpencadcRegistryClient.lookupServiceURL("ivo://cadc.nrc.ca/vospace", "ivo://ivoa.net/std/VOSpace/v2.0#transfers", "vs:ParamHTTP") OpencadcRegistryClient.lookupServiceURL("ivo://cadc.nrc.ca/vospace", "ivo://ivoa.net/std/VOSpace#sync-2.1", "vs:ParamHTTP") |> HTTPoison.post!( toXML(destinationURI, transferServiceURL), [{"Content-Type", "text/xml"}, {"Cookie", "CADC_SSO=\"" <> cookieCredential <> "\""}] ) |> transfer(sourceStream, destinationURI, cookieCredential) end defp write(%HTTPoison.Response{status_code: 401}, _, destinationURI, _) do raise ArgumentError, message: "Write permission denied on " <> destinationURI end defp write(%HTTPoison.Response{status_code: 404}, _, destinationURI, _) do raise ArgumentError, message: "No such target " <> destinationURI end defp transfer(%HTTPoison.Response{status_code: 303} = response, sourceStream, _, cookieCredential) do response.headers |> get_header("Location") |> HTTPoison.get!( [{"Cookie", "CADC_SSO=\"" <> cookieCredential <> "\""}] ) |> upload_to(sourceStream, cookieCredential) end defp transfer(response, _, destinationURI, _) do raise ArgumentError, message: "Unable to transfer to " <> destinationURI <> "\n" <> response.body end defp upload_to(%HTTPoison.Response{status_code: 303} = response, sourceStream, cookieCredential) do response.headers |> get_header("Location") |> HTTPoison.get!( [{"Cookie", "CADC_SSO=\"" <> cookieCredential <> "\""}] ) |> upload_to(sourceStream, cookieCredential) end defp upload_to(%HTTPoison.Response{status_code: 200} = response, sourceStream, cookieCredential) do Parser.all(Parser.from_string(response.body), "//vos:transfer/vos:protocol[@uri=\"ivo://ivoa.net/vospace/core#httpput\"]/vos:endpoint") |> extractTransferEndpointURL |> HTTPoison.put!({:stream, sourceStream}, [{"Cookie", "CADC_SSO=\"" <> cookieCredential <> "\""}]) end defp upload_to(response, _, _) do raise ArgumentError, message: response.body end defp extractTransferEndpointURL([]) do raise ArgumentError, message: "No service endpoints found to transfer to." end defp extractTransferEndpointURL(endpoints) do endpoints |> Enum.at(0) |> Parser.text end defp toXML(destinationURI) do " destinationURI <> "\" busy=\"false\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"vos:DataNode\" xmlns:vos=\"http://www.ivoa.net/xml/VOSpace/v2.1\">" end defp toXML(destinationURI, transferURL) do "" <> destinationURI <> "pushToVoSpace" <> transferURL <> "true" end defp get_header(headers, key) do headers |> Enum.filter(fn({k, _}) -> k == key end) |> hd |> elem(1) end end