defmodule Aliyun.Oss.Object do
@moduledoc """
Object operations - basic operations.
Other operations can be found in:
- `Aliyun.Oss.Object`
- `Aliyun.Oss.Object.MultipartUpload`
- `Aliyun.Oss.Object.ACL`
- `Aliyun.Oss.Object.Symlink`
- `Aliyun.Oss.Object.Tagging`
"""
alias Aliyun.Oss.Config
alias Aliyun.Oss.Service
alias Aliyun.Oss.Client.{Request, Response, Error}
@type error() ::
%Error{body: String.t(), status_code: integer(), parsed_details: map()} | atom()
@doc """
HeadObject - gets the metadata of an object.
The content of the object is not returned.
## Examples
iex> Aliyun.Oss.Object.head_object(config, "some-bucket", "some-object")
{:ok,
%Aliyun.Oss.Client.Response{
data: "",
headers: [
{"Server", "AliyunOSS"},
{"Date", "Wed, 05 Dec 2018 05:50:02 GMT"},
{"Content-Type", "application/octet-stream"},
{"Content-Length", "0"},
{"Connection", "keep-alive"},
{"x-oss-request-id", "5C0000000000000000000000"},
{"Accept-Ranges", "bytes"},
{"ETag", "\"D4100000000000000000000000000000\""},
{"Last-Modified", "Mon, 15 Oct 2018 01:38:47 GMT"},
{"x-oss-object-type", "Normal"},
{"x-oss-hash-crc64ecma", "0"},
{"x-oss-storage-class", "IA"},
{"Content-MD5", "1B2M2Y8AsgTpgAmY7PhCfg=="},
{"x-oss-server-time", "19"}
]
}}
iex> Aliyun.Oss.Object.head_object(config, "some-bucket", "unknown-object")
{:error, %Aliyun.Oss.Client.Error{status_code: 404, body: "", parsed_details: nil}}
"""
@spec head_object(Config.t(), String.t(), String.t(), map(), map()) ::
{:error, error()} | {:ok, Response.t()}
def head_object(%Config{} = config, bucket, object, headers \\ %{}, sub_resources \\ %{}) do
Service.head(config, bucket, object, headers: headers, sub_resources: sub_resources)
end
@doc """
GetObjectMeta - gets the metadata of an object, including ETag, Size, and LastModified.
The content of the object is not returned.
## Examples
iex> Aliyun.Oss.Object.get_object_meta(config, "some-bucket", "some-object")
{:ok,
%Aliyun.Oss.Client.Response{
data: "",
headers: [
{"Server", "AliyunOSS"},
{"Date", "Wed, 05 Dec 2018 05:50:02 GMT"},
{"Content-Length", "0"},
{"Connection", "keep-alive"},
{"x-oss-request-id", "5C0000000000000000000000"},
{"ETag", "\"D4100000000000000000000000000000\""},
{"x-oss-hash-crc64ecma", "0"},
{"Last-Modified", "Mon, 15 Oct 2018 01:38:47 GMT"},
{"x-oss-server-time", "19"}
]
}}
"""
@spec get_object_meta(Config.t(), String.t(), String.t()) ::
{:error, error()} | {:ok, Response.t()}
def get_object_meta(%Config{} = config, bucket, object) do
head_object(config, bucket, object, %{}, %{"objectMeta" => nil})
end
@doc """
GetObject - gets an object.
## Examples
iex> Aliyun.Oss.Object.get_object(config, "some-bucket", "some-object")
{:ok, %Aliyun.Oss.Client.Response{
data: <<208, 207, 17, 224, 161, 177, 26, 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 62, 0, 3, 0, 254, 255, 9, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18,
0, 0, 0, ...>>,
headers: [
{"Date", "Wed, 05 Dec 2018 02:34:57 GMT"},
...
]
}
}
"""
@spec get_object(Config.t(), String.t(), String.t(), map(), map()) ::
{:error, error()} | {:ok, Response.t()}
def get_object(%Config{} = config, bucket, object, headers \\ %{}, sub_resources \\ %{}) do
Service.get(config, bucket, object, headers: headers, sub_resources: sub_resources)
end
@doc """
SelectObject - executes SQL statements to perform operations on an object and obtains the execution results.
## Options
- `:format` - specifies the request syntax:
- available values: `:json`, `:csv`
- default value: `:csv`
## Examples
iex> select_request = %{
"SelectRequest" => %{
"Expression" => "c2VsZWN0ICogZnJvbSBvc3NvYmplY3QuY29udGFjdHNbKl0gcyB3aGVyZSBzLmFnZSA9IDI3",
"InputSerialization" => %{"JSON" => %{"Type" => "DOCUMENT"}},
"OutputSerialization" => %{"JSON" => %{"RecordDelimiter" => "LA=="}}
}
}
iex> Aliyun.Oss.Object.select_object(config, "some-bucket", "some-object", select_request, format: :json)
{:ok, %Aliyun.Oss.Client.Response{
data: <<208, 207, 17, 224, 161, 177, 26, 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 62, 0, 3, 0, 254, 255, 9, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18,
0, 0, 0, ...>>,
headers: [
{"Date", "Wed, 05 Dec 2018 02:34:57 GMT"},
...
]
}
}
iex> select_request = ~S[
<SelectRequest>
<Expression>c2VsZWN0ICogZnJvbSBvc3NvYmplY3QuY29udGFjdHNbKl0gcyB3aGVyZSBzLmFnZSA9IDI3</Expression>
<InputSerialization>
<JSON>
<Type>DOCUMENT</Type>
</JSON>
</InputSerialization>
<OutputSerialization>
<JSON>
<RecordDelimiter>LA==</RecordDelimiter>
</JSON>
</OutputSerialization>
</SelectRequest>
]
iex> Aliyun.Oss.Object.select_object(config, "some-bucket", "some-object", select_request, format: :json)
{:ok, %Aliyun.Oss.Client.Response{
data: <<208, 207, 17, 224, 161, 177, 26, 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 62, 0, 3, 0, 254, 255, 9, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18,
0, 0, 0, ...>>,
headers: [
{"Date", "Wed, 05 Dec 2018 02:34:57 GMT"},
...
]
}
}
"""
@spec select_object(Config.t(), String.t(), String.t(), String.t() | map(), keyword) ::
{:error, error()} | {:ok, Response.t()}
def select_object(config, bucket, object, select_request, options \\ [])
def select_object(config, bucket, object, %{} = select_request, options) do
select_object(config, bucket, object, MapToXml.from_map(select_request), options)
end
def select_object(config, bucket, object, select_request, options) do
x_oss_process =
case Keyword.get(options, :format, :csv) do
:csv -> "csv/select"
:json -> "json/select"
end
post_object(config, bucket, object, select_request, %{}, %{"x-oss-process" => x_oss_process})
end
@doc """
CreateSelectObjectMeta - 获取目标文件总的行数,总的列数(对于 CSV 文件),以及 Splits 个数。
如果该信息不存在,则会扫描整个文件,分析并记录下 CSV 文件的上述信息。重复调用则会保存上述信息而不必重新扫描整个文件。
TODO: fix the doc
## Options
- `:format` - specifies the request syntax:
- available values: `:json`, `:csv`
- default value: `:csv`
## Examples
iex> select_request = %{
"JsonMetaRequest" => %{
"InputSerialization" => %{"JSON" => %{"Type" => "LINES"}},
"OverwriteIfExisting" => "false"
}
}
iex> Aliyun.Oss.Object.select_object_meta(config, "some-bucket", "some-object", select_request, format: :json)
{:ok, %Aliyun.Oss.Client.Response{
data: <<208, 207, 17, 224, 161, 177, 26, 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 62, 0, 3, 0, 254, 255, 9, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18,
0, 0, 0, ...>>,
headers: [
{"Date", "Wed, 05 Dec 2018 02:34:57 GMT"},
...
]
}
}
iex> select_request = ~S[
<?xml version="1.0"?>
<JsonMetaRequest>
<InputSerialization>
<JSON>
<Type>LINES</Type>
</JSON>
</InputSerialization>
<OverwriteIfExisting>false</OverwriteIfExisting>
</JsonMetaRequest>
]
iex> Aliyun.Oss.Object.select_object_meta(config, "some-bucket", "some-object", select_request, format: :json)
{:ok, %Aliyun.Oss.Client.Response{
data: <<208, 207, 17, 224, 161, 177, 26, 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 62, 0, 3, 0, 254, 255, 9, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18,
0, 0, 0, ...>>,
headers: [
{"Date", "Wed, 05 Dec 2018 02:34:57 GMT"},
...
]
}
}
"""
@spec select_object_meta(Config.t(), String.t(), String.t(), String.t() | map(), keyword) ::
{:error, error()} | {:ok, Response.t()}
def select_object_meta(config, bucket, object, select_request, options \\ [])
def select_object_meta(config, bucket, object, %{} = select_request, options) do
select_object_meta(config, bucket, object, MapToXml.from_map(select_request), options)
end
def select_object_meta(config, bucket, object, select_request, options) do
x_oss_process =
case Keyword.get(options, :format, :csv) do
:csv -> "csv/meta"
:json -> "json/meta"
end
post_object(config, bucket, object, select_request, %{}, %{"x-oss-process" => x_oss_process})
end
@doc """
Creates a signed URL for an object.
## Examples
iex> expires = Timex.now() |> Timex.shift(days: 1) |> Timex.to_unix()
iex> Aliyun.Oss.Object.signed_url(config, "some-bucket", "some-object", expires, "GET", %{"Content-Type" => ""})
"http://some-bucket.oss-cn-hangzhou.aliyuncs.com/oss-api.pdf?OSSAccessKeyId=nz2pc5*******9l&Expires=1141889120&Signature=vjbyPxybdZ*****************v4%3D"
iex> Aliyun.Oss.Object.signed_url(config, "some-bucket", "some-object", expires, "PUT", %{"Content-Type" => "text/plain"})
"http://some-bucket.oss-cn-hangzhou.aliyuncs.com/oss-api.pdf?OSSAccessKeyId=nz2pc5*******9l&Expires=1141889120&Signature=vjbyPxybdZ*****************v4%3D"
"""
@spec signed_url(Config.t(), String.t(), String.t(), integer(), String.t(), map(), map()) ::
String.t()
def signed_url(config, bucket, object, expires, method, headers, sub_resources \\ %{}) do
request =
Request.build(%{
verb: method,
host: "#{bucket}.#{config.endpoint}",
path: "/#{object}",
resource: "/#{bucket}/#{object}",
sub_resources: sub_resources,
headers: headers,
expires: expires
})
Request.to_signed_url(config, request)
end
@doc """
Creates a signed URL for accessing an object.
## Examples
iex> expires = Timex.now() |> Timex.shift(days: 1) |> Timex.to_unix()
iex> Aliyun.Oss.Object.object_url(config, "some-bucket", "some-object", expires)
"http://some-bucket.oss-cn-hangzhou.aliyuncs.com/oss-api.pdf?OSSAccessKeyId=nz2pc56s936**9l&Expires=1141889120&Signature=vjbyPxybdZaNmGa%2ByT272YEAiv4%3D"
"""
@spec object_url(Config.t(), String.t(), String.t(), integer()) :: String.t()
def object_url(config, bucket, object, expires) do
signed_url(config, bucket, object, expires, "GET", %{"Content-Type" => ""})
end
@doc """
PutObject - uploads objects.
## Examples
iex> Aliyun.Oss.Object.put_object(config, "some-bucket", "some-object", "CONTENT")
{:ok, %Aliyun.Oss.Client.Response{
data: "",
headers: [
{"Server", "AliyunOSS"},
{"Date", "Wed, 05 Dec 2018 02:34:57 GMT"},
...
]
}
}
"""
@spec put_object(Config.t(), String.t(), String.t(), String.t(), map(), map()) ::
{:error, error()} | {:ok, Response.t()}
def put_object(config, bucket, object, body, headers \\ %{}, sub_resources \\ %{}) do
Service.put(config, bucket, object, body, headers: headers, sub_resources: sub_resources)
end
@doc """
CopyObject - copies objects within a bucket or between buckets in the same region.
## Examples
iex> Aliyun.Oss.Object.copy_object(config, {"source-bucket", "source-object"}, {"target-bucket", "target-object"})
{:ok, %Aliyun.Oss.Client.Response{
data: %{
"CopyObjectResult" => %{
"ETag" => "\"D2D50000000000000000000000000000\"",
"LastModified" => "2019-02-27T09:21:13.000Z"
}
},
headers: [
{"Server", "AliyunOSS"},
{"Date", "Wed, 05 Dec 2018 02:34:57 GMT"},
...
]
}
}
"""
@spec copy_object(Config.t(), {String.t(), String.t()}, {String.t(), String.t()}, map()) ::
{:error, error()} | {:ok, Response.t()}
def copy_object(
config,
{source_bucket, source_object},
{target_bucket, target_object},
headers \\ %{}
) do
headers = Map.put(headers, "x-oss-copy-source", "/#{source_bucket}/#{source_object}")
put_object(config, target_bucket, target_object, "", headers)
end
@doc """
AppendObject - uploads an object by appending the content of the object to an existing object.
## Examples
iex> Aliyun.Oss.Object.append_object(config, "some-bucket", "some-object", "CONTENT", 0)
{:ok, %Aliyun.Oss.Client.Response{
data: "",
headers: [
{"Server", "AliyunOSS"},
{"Date", "Fri, 01 Mar 2019 05:57:23 GMT"},
{"Content-Length", "0"},
{"Connection", "keep-alive"},
{"x-oss-request-id", "5C0000000000000000000000"},
{"ETag", "\"B38D0000000000000000000000000000\""},
{"x-oss-next-append-position", "10"},
{"x-oss-hash-crc64ecma", "8000000000000000000"},
{"x-oss-server-time", "17"}
]
}
}
"""
@spec append_object(Config.t(), String.t(), String.t(), String.t(), integer(), map()) ::
{:error, error()} | {:ok, Response.t()}
def append_object(config, bucket, object, body, position, headers \\ %{}) do
post_object(config, bucket, object, body, headers, %{"append" => nil, "position" => position})
end
@doc """
RestoreObject - restores an Archive object or a Cold Archive object.
## Examples
iex> Aliyun.Oss.Object.restore_object(config, "some-bucket", "some-object")
{:ok, %Aliyun.Oss.Client.Response{
data: "",
headers: [
{"Server", "AliyunOSS"},
{"Date", "Fri, 01 Mar 2019 06:38:21 GMT"},
{"Content-Length", "0"},
{"Connection", "keep-alive"},
{"x-oss-request-id", "5C7000000000000000000000"},
{"x-oss-server-time", "7"}
]
}
}
"""
@spec restore_object(Config.t(), String.t(), String.t()) ::
{:error, error()} | {:ok, Response.t()}
def restore_object(config, bucket, object) do
post_object(config, bucket, object, "", %{}, %{"restore" => nil})
end
@doc """
DeleteObject - deletes an object.
## Examples
iex> Aliyun.Oss.Object.delete_object(config, "some-bucket", "some-object")
{:ok, %Aliyun.Oss.Client.Response{
data: "",
headers: [
{"Server", "AliyunOSS"},
{"Date", "Wed, 05 Dec 2018 02:34:57 GMT"},
...
]
}
}
"""
@spec delete_object(Config.t(), String.t(), String.t()) ::
{:error, error()} | {:ok, Response.t()}
def delete_object(config, bucket, object, sub_resources \\ %{}) do
Service.delete(config, bucket, object, sub_resources: sub_resources)
end
@doc """
DeleteMultipleObjects - deletes multiple objects from a bucket.
## Options
- `:encoding_type` - Accept value: `:url`
- `:quiet` - Set `true` to enable the quiet mode, default is `false`
## Examples
iex> Aliyun.Oss.Object.delete_multiple_objects(config, "some-bucket", ["object1", "object2"])
{:ok, %Aliyun.Oss.Client.Response{
data: %{
"DeleteResult" => %{
"Deleted" => [%{"key" => "object1"}, %{"key" => "object2"}]
}
},
headers: [
{"Server", "AliyunOSS"},
{"Date", "Wed, 05 Dec 2018 02:34:57 GMT"},
...
]
}
}
"""
@body_tmpl """
<?xml version="1.0" encoding="UTF-8"?>
<Delete>
<Quiet><%= quiet %></Quiet>
<%= for object <- objects do %>
<Object><Key><%= object %></Key></Object>
<% end %>
</Delete>
"""
@spec delete_multiple_objects(Config.t(), String.t(), [String.t()],
encoding_type: :url,
quiet: boolean()
) ::
{:error, error()} | {:ok, Response.t()}
def delete_multiple_objects(config, bucket, objects, options \\ []) do
headers =
case options[:encoding_type] do
:url -> %{"encoding-type" => "url"}
_ -> %{}
end
quiet = Keyword.get(options, :quiet, false)
body = EEx.eval_string(@body_tmpl, quiet: quiet, objects: objects)
Service.post(config, bucket, nil, body, headers: headers, sub_resources: %{"delete" => nil})
end
@doc """
Signs Post Policy, and returns the encoded Post Policy and its signature.
## Examples
iex> policy = %{
...> "conditions" => [
...> ["content-length-range", 0, 10485760],
...> %{"bucket" => "ahaha"},
...> %{"A" => "a"},
...> %{"key" => "ABC"}
...> ],
...> "expiration" => "2013-12-01T12:00:00Z"
...>}
iex> Aliyun.Oss.Object.sign_post_policy(config, policy)
%{
policy: "eyJjb25kaXRpb25zIjpbWyJjb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MF0seyJidWNrZXQiOiJhaGFoYSJ9LHsiQSI6ImEifSx7ImtleSI6IkFCQyJ9XSwiZXhwaXJhdGlvbiI6IjIwMTMtMTItMDFUMTI6MDA6MDBaIn0=",
signature: "W835KpLsL6k1/oo28RcsEflB6hw="
}
"""
@spec sign_post_policy(Config.t(), map()) :: %{
policy: String.t(),
signature: String.t()
}
def sign_post_policy(config, %{} = policy) do
secret = config.access_key_secret
encoded_policy = policy |> Jason.encode!() |> Base.encode64()
%{
policy: encoded_policy,
signature: Aliyun.Util.Sign.sign(encoded_policy, secret)
}
end
defp post_object(config, bucket, object, body, headers, sub_resources) do
Service.post(config, bucket, object, body, headers: headers, sub_resources: sub_resources)
end
end