Current section
Files
Jump to
Current section
Files
lib/rushie.ex
defmodule Rushie do
@moduledoc """
API for Rushie
"""
alias Rushie.FileMeta
alias Rushie.Login
alias Rushie.ManagedShare
@doc """
Login to Rushfiles. You need to login before you can actually do anything
with Rushie. Most other Rushie functions need a login struct as first argument.
"""
@spec login(domain :: String.t, email :: String.t, password :: String.t) :: {:ok, Login.t} | {:error, any}
def login(domain, email, password) do
with {:ok, login} <- Login.login(domain, email, password) do
Login.gateway_login(login)
end
end
@doc """
Upload a file to a Rushfiles share
"""
@spec upload_file(login :: Login.t, share :: ManagedShare.t, file_path :: String.t) :: {:ok, FileMeta.t} | {:error, any}
def upload_file(%Login{} = login, %ManagedShare{} = share, file_path, target_filename \\ nil) do
with {:ok, data} <- File.read(file_path),
file_name <- target_filename || Path.basename(file_path),
{:ok, :put, url} <- file_event(login, 0, share, {file_name, data}) do
put_file(login, url, data)
end
end
@doc """
List all files in a Rushfiles share
"""
@spec list_files(login :: Login.t, share :: ManagedShare.t) :: {:ok, [FileMeta.t]} | {:error, any}
def list_files(%Login{} = login, %ManagedShare{} = share) do
url = "https://clientgateway.#{login.domain}/api/shares/#{share.share_id}/children"
headers = ["Authorization": "DomainToken #{login.token}"]
case HTTPoison.get(url, headers, httpoison_options()) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
parse_list_response(body)
other ->
{:error, {__MODULE__, :list_files, other}}
end
end
@doc """
Download a specific file from a Rushfile share.
`local_file` is the full path to write the file locally.
"""
@spec download_file(login :: Login.t, share :: ManagedShare.t, file :: FileMeta.t, local_file :: String.t) :: :ok | {:error, any}
def download_file(%Login{} = login, %ManagedShare{} = share, %FileMeta{} = file, local_file) do
file_cache_url = List.first(login.file_cache_urls)
headers = ["Authorization": "DomainToken #{login.token}"]
url = "https://#{file_cache_url}/api/shares/#{share.share_id}/files/#{file.upload_name}"
case HTTPoison.get(url, headers, httpoison_options()) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
File.write(local_file, body)
other -> {:error, {__MODULE__, :get_file, other}}
end
end
@doc """
Internal.
event_type: 0 = create
"""
def file_event(%Login{} = login, event_type, %ManagedShare{} = share, file_data) do
file_cache_url = List.first(login.file_cache_urls)
headers = ["Content-type": "application/json", "Authorization": "DomainToken #{login.token}"]
utc_now = DateTime.to_unix(DateTime.utc_now(), :milliseconds)
url = "https://#{file_cache_url}/api/shares/#{share.share_id}/files"
body = %{
"TransmitId" => "rushieguid_" <> to_string(utc_now),
# "UserOrigin" => login.email,
"ClientJournalEventType" => event_type,
# "TransmitDate" => utc_now,
"RfVirtualFile" => rf_virtual_file(share, file_data),
# "ShareToken" => share.acl_token,
"DeviceId" => "test_device_id"
}
case HTTPoison.post(url, Poison.encode!(body), headers, httpoison_options()) do
{:ok, %HTTPoison.Response{status_code: 202, body: body}} ->
parse_file_event(body)
other -> {:error, {__MODULE__, :file_event, other}}
end
end
def put_file(%Login{} = login, url, data) do
data_size = byte_size(data)
headers = [
"Authorization": "DomainToken #{login.token}",
"Content-Range": "bytes 0-#{data_size - 1}/#{data_size}"
]
case HTTPoison.put(url, data, headers, httpoison_options()) do
{:ok, %HTTPoison.Response{status_code: 201, body: body}} ->
parse_put_file(body)
other -> {:error, {__MODULE__, :put_file, other}}
end
end
def parse_file_event(body) do
with {:ok, value} <- Poison.decode(body) do
{:ok, :put, get_in(value, ["Data", "Url"])}
end
end
def parse_put_file(body) do
with {:ok, value} <- Poison.decode(body),
1 <- get_in(value, ["Code"]) do
meta = value
|> get_in(["Data", "ClientJournalEvent", "RfVirtualFile"])
|> FileMeta.parse_file_meta()
{:ok, meta}
else
value when is_number(value) ->
{:error, {__MODULE__, :parse_put_response, "Unexpected Code: #{value}"}}
other -> other
end
end
defp rf_virtual_file(%ManagedShare{} = share, {file_name, file_data}) do
now = DateTime.utc_now()
%{
"InternalName" => file_name,
# "Tick" => 1,
"ShareId" => share.share_id,
# "ShareTick" => 1,
"ParrentId" => share.share_id,
"EndOfFile" => byte_size(file_data),
"PublicName" => file_name,
"CreationTime" => DateTime.to_iso8601(now),
"LastAccessTime" => DateTime.to_iso8601(now),
"LastWriteTime" => DateTime.to_iso8601(now),
"Attributes" => 128,
# "Deleted" => false,
"FilehHash" => Base.encode16(:erlang.md5(file_data), case: :lower)
}
end
def parse_list_response(body) do
with {:ok, value} <- Poison.decode(body) do
metas = Enum.map(get_in(value, ["Data"]) || [], &FileMeta.parse_file_meta/1)
{:ok, metas}
end
end
def httpoison_options do
[
timeout: :timer.seconds(50),
recv_timeout: :timer.minutes(5)
]
end
end