diff --git a/.gitignore b/.gitignore index c922fdb..c6bc6c0 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ erl_crash.dump # Ignore package tarball (built via "mix hex.build"). az-*.tar +.DS_Store \ No newline at end of file diff --git a/config/test.exs b/config/test.exs index bdab617..5505d02 100644 --- a/config/test.exs +++ b/config/test.exs @@ -10,4 +10,4 @@ config :ex_azure_storage, # config :ex_azure_storage, http_adapter: HttpClientMock # Print only warnings and errors during test -config :logger, level: :warn +config :logger, level: :warning diff --git a/fixtures/2WE4HZPD57KVPL4RQKCB4PUG42SQ2RMJ.pdf b/fixtures/2WE4HZPD57KVPL4RQKCB4PUG42SQ2RMJ.pdf new file mode 100644 index 0000000..c323fad Binary files /dev/null and b/fixtures/2WE4HZPD57KVPL4RQKCB4PUG42SQ2RMJ.pdf differ diff --git a/fixtures/2ZJG56HD3YEZGEQZL4RFOYNIMTZISHDX.pdf b/fixtures/2ZJG56HD3YEZGEQZL4RFOYNIMTZISHDX.pdf new file mode 100644 index 0000000..5d46783 Binary files /dev/null and b/fixtures/2ZJG56HD3YEZGEQZL4RFOYNIMTZISHDX.pdf differ diff --git a/fixtures/MYKPH7XVYJANM6NEUFMBWLHISDBHMI2C.pdf b/fixtures/MYKPH7XVYJANM6NEUFMBWLHISDBHMI2C.pdf new file mode 100644 index 0000000..d7f75f5 Binary files /dev/null and b/fixtures/MYKPH7XVYJANM6NEUFMBWLHISDBHMI2C.pdf differ diff --git a/lib/azure_storage/azure_blob.ex b/lib/azure_storage/azure_blob.ex index cbd9811..1bcbaca 100644 --- a/lib/azure_storage/azure_blob.ex +++ b/lib/azure_storage/azure_blob.ex @@ -83,24 +83,32 @@ defmodule AzureStorage.Blob do | {:error, String.t()} def list_blobs(%Context{service: "blob"} = context, container, options \\ []) do {:ok, opts} = NimbleOptions.validate(options, Schema.list_blobs_options()) - max_results = opts[:max_results] + + max_results = + case opts[:maxresults] do + nil -> "" + _ -> "&maxresults=#{opts[:maxresults]}" + end prefix = - case String.length(opts[:prefix]) do - 0 -> [] - _ -> ["&prefix=", opts[:prefix]] + case opts[:prefix] do + nil -> "" + _ -> "&prefix=#{opts[:prefix]}" + end + + delimiter = + case opts[:delimiter] do + nil -> "" + _ -> "&delimiter=#{opts[:delimiter]}" end - query = - ([ - container, - "?restype=container", - container, - "&comp=list", - "&maxresults=", - max_results - ] ++ prefix) - |> IO.iodata_to_binary() + marker = + case opts[:marker] do + nil -> "" + _ -> "&marker=#{opts[:marker]}" + end + + query = "#{container}?restype=container&comp=list#{max_results}#{prefix}#{delimiter}#{marker}" context |> build(method: :get, path: query) @@ -157,8 +165,8 @@ defmodule AzureStorage.Blob do ``` {:ok, context} = AzureStorage.create_blob_service("account_name", "account_key") context |> put_blob("blobs", - "cache-key-1.json", - "{\\"data\\": []}", + "cache-key-1.json", + "{\\"data\\": []}", content_type: "application/json;charset=\\"utf-8\\"" ) ``` @@ -185,6 +193,25 @@ defmodule AzureStorage.Blob do |> parse_body_response() end + def put_binary_blob( + %Context{service: "blob"} = context, + container, + filename, + bytes, + options \\ [] + ) do + {:ok, opts} = NimbleOptions.validate(options, Schema.put_blob_options()) + query = "#{container}/#{filename}" + headers = %{ + "x-ms-blob-type" => "BlockBlob", + :"Content-Type" => "application/octet-stream" + } + context + |> build(method: :put, path: query, body: bytes, headers: headers) + |> request(recv_timeout: opts[:recv_timeout], timeout: opts[:timeout]) + |> parse_body_response() + end + @doc """ Acquires a new lease. If container and blob are specified, acquires a blob lease. Otherwise, if only container is specified and blob is null, acquires a container lease. @@ -309,7 +336,7 @@ defmodule AzureStorage.Blob do path = case String.starts_with?(opts[:path], "/") do - true -> String.slice(opts[:path], 1..-1) + true -> String.slice(opts[:path], 1..-1//1) false -> opts[:path] end diff --git a/lib/azure_storage/blob/schema.ex b/lib/azure_storage/blob/schema.ex index 605fa27..59645d2 100644 --- a/lib/azure_storage/blob/schema.ex +++ b/lib/azure_storage/blob/schema.ex @@ -33,6 +33,16 @@ defmodule AzureStorage.Blob.Schema do type: :string, doc: "Content-Type", default: "text/plain;charset=\"utf-8\"" + ], + recv_timeout: [ + type: :pos_integer, + default: 30_000, + required: false + ], + timeout: [ + type: :pos_integer, + default: 30_000, + required: false ] ] diff --git a/lib/azure_storage/http_client.ex b/lib/azure_storage/http_client.ex index 52dbb47..2ceb2ab 100644 --- a/lib/azure_storage/http_client.ex +++ b/lib/azure_storage/http_client.ex @@ -6,7 +6,6 @@ defmodule Http.Client do def request(method, url, body, headers, options) do http_options = get_http_request_options(options) - http_adapter().request(method, url, body, headers, http_options) |> process_response end diff --git a/lib/azure_storage/request/context.ex b/lib/azure_storage/request/context.ex index aefdd01..ba948c6 100644 --- a/lib/azure_storage/request/context.ex +++ b/lib/azure_storage/request/context.ex @@ -58,14 +58,24 @@ defmodule AzureStorage.Request.Context do headers_cfg = options[:headers] # TODO: improve headers - content_length = - case String.length(body) do - 0 -> - %{} + content_length = case headers_cfg[:"Content-Type"] do + "application/octet-stream" -> + case Kernel.byte_size(body) do + 0 -> + %{} + + value -> + %{:"content-length" => "#{value}"} + end + _ -> + case String.length(body) do + 0 -> + %{} - value -> - %{:"content-length" => "#{value}"} - end + value -> + %{:"content-length" => "#{value}"} + end + end headers = default_headers diff --git a/lib/azure_storage/request/schema.ex b/lib/azure_storage/request/schema.ex index fa91c51..50eadbc 100644 --- a/lib/azure_storage/request/schema.ex +++ b/lib/azure_storage/request/schema.ex @@ -29,6 +29,14 @@ defmodule AzureStorage.Request.Schema do response_body: [ type: {:in, [:full, :json]}, default: :json + ], + recv_timeout: [ + type: :pos_integer, + default: 30_000 + ], + timeout: [ + type: :pos_integer, + default: 30_000 ] ] end diff --git a/mix.exs b/mix.exs index 090a0e8..5b2a6be 100644 --- a/mix.exs +++ b/mix.exs @@ -42,7 +42,7 @@ defmodule AzureStorage.MixProject do {:elixir_xml_to_map, "~> 2.0"}, {:jason, "~> 1.2"}, {:httpoison, "~> 1.8.0"}, - {:nimble_options, "~> 0.3.5"}, + {:nimble_options, "~> 1.1.1"}, {:uuid, "~> 1.1", only: :test, runtime: false}, {:ex_doc, "~> 0.23", only: :dev, runtime: false}, {:credo, "~> 1.4", only: [:dev, :test], runtime: false} diff --git a/mix.lock b/mix.lock index 227eb20..76dc517 100644 --- a/mix.lock +++ b/mix.lock @@ -16,10 +16,10 @@ "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "nimble_options": {:hex, :nimble_options, "0.3.5", "a4f6820cdcb4ee444afd78635f323e58e8a5ddf2fbbe9b9d283a99f972034bae", [:mix], [], "hexpm", "f5507cc90033a8d12769522009c80aa9164af6bab245dbd4ad421d008455f1e1"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, } diff --git a/test/list_blobs_test.exs b/test/list_blobs_test.exs new file mode 100644 index 0000000..76000d4 --- /dev/null +++ b/test/list_blobs_test.exs @@ -0,0 +1,31 @@ +defmodule TestExAzureStorage do + use ExUnit.Case + alias AzureStorage.Blob + + @account_name Application.compile_env(:ex_azure_storage, :account_name, "") + @account_key Application.compile_env(:ex_azure_storage, :account_key, "") + @container "tmp" + + setup_all do + {:ok, context} = AzureStorage.create_blob_service(@account_name, @account_key) + %{context: context} + end + + test "can paginate", %{context: context} do + first_page = Blob.list_blobs(context, @container, maxresults: 1) + {:ok, %{items: %{ + "Name" => name, + "Properties" => properties + }, marker: marker}} = first_page + assert is_binary(name) + assert is_map(properties) + + second_page = Blob.list_blobs(context, @container, marker: marker, maxresults: 1) + {:ok, %{items: %{ + "Name" => name, + "Properties" => properties + }}} = second_page + assert is_binary(name) + assert is_map(properties) + end +end diff --git a/test/put_blob_test.exs b/test/put_blob_test.exs new file mode 100644 index 0000000..e03e23f --- /dev/null +++ b/test/put_blob_test.exs @@ -0,0 +1,41 @@ +defmodule TestExAzureStorage do + use ExUnit.Case + alias AzureStorage.Blob + + @account_name Application.compile_env(:ex_azure_storage, :account_name, "") + @account_key Application.compile_env(:ex_azure_storage, :account_key, "") + @container "tmp" + + setup_all do + {:ok, context} = AzureStorage.create_blob_service(@account_name, @account_key) + %{context: context} + end + + test "can put binary blob 1", %{context: context} do + file_path = "fixtures/MYKPH7XVYJANM6NEUFMBWLHISDBHMI2C.pdf" + put_binary_blob(file_path, context) + end + + test "can put binary blob 2", %{context: context} do + file_path = "fixtures/2ZJG56HD3YEZGEQZL4RFOYNIMTZISHDX.pdf" + put_binary_blob(file_path, context) + end + + test "can put binary blob 3", %{context: context} do + file_path = "fixtures/2WE4HZPD57KVPL4RQKCB4PUG42SQ2RMJ.pdf" + put_binary_blob(file_path, context) + end + + test "can put with timeout", %{context: context} do + file_path = "fixtures/2WE4HZPD57KVPL4RQKCB4PUG42SQ2RMJ.pdf" + put_binary_blob(file_path, context, timeout: 60000, recv_timeout: 60000) + end + + defp put_binary_blob(file_path, context, options \\ []) do + file = File.open!(file_path) + content = IO.binread(file, :eof) + [file_name | _] = file_path |> String.split("/") |> Enum.reverse() + remote_file_path = "test/#{file_name}" + {:ok, _} = Blob.put_binary_blob(context, @container, remote_file_path, content, options) + end +end