Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

pulled ypaq's contrib, bumped to v2.0.0

  • Loading branch information...
commit 5ba2f370dc9a5c1d8a476cc8dd5bec97c6297a36 2 parents 48256e5 + a91929c
David Dossot authored
2  LICENSE
... ... @@ -1,6 +1,6 @@
1 1 This is the MIT license.
2 2
3   - Copyright (c) 2010 David Dossot
  3 + Copyright (c) 2010-2013 David Dossot
4 4
5 5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 6 of this software and associated documentation files (the "Software"), to deal
148 README.md
Source Rendered
... ... @@ -1,16 +1,13 @@
1   -Rackspace Cloud Files Erlang Client
2   -===================================
  1 +# Rackspace Cloud Files Erlang Client
3 2
4   -Description
5   ------------
  3 +## Description
6 4
7 5 This is an Erlang interface into the Rackspace Cloud Files service. It has been largely inspired by the existing [Ruby](http://github.com/rackspace/ruby-cloudfiles) API.
8 6
9 7
10   -Building
11   ---------
  8 +## Building
12 9
13   -*cferl* relies on [rebar](http://bitbucket.org/basho/rebar/wiki/Home) for its build and dependency management and targets Erlang/OTP R13B04 or above.
  10 +**cferl** relies on [rebar](http://bitbucket.org/basho/rebar/wiki/Home) for its build and dependency management and targets Erlang/OTP R13B04 or above.
14 11
15 12 Simply run:
16 13
@@ -27,10 +24,9 @@ Optionally, to run the integration tests (and generate the code samples visible
27 24 If you run the integration tests, you'll need your API key and at least one pre-existing container. Note that a test container will be created and some queries could take a while if you have lots of containers.
28 25
29 26
30   -Using
31   ------
  27 +## Using
32 28
33   -*cferl* requires that the ssl and ibrowse applications be started prior to using it.
  29 +**cferl** requires that the ssl and ibrowse applications be started prior to using it.
34 30
35 31 The following, which is output when running the integration tests, demonstrates a typical usage of the API. Refer to the documentation for the complete reference.
36 32
@@ -39,145 +35,145 @@ The following, which is output when running the integration tests, demonstrates
39 35 {ok,CloudFiles}=cferl:connect(Username,ApiKey).
40 36
41 37 # Retrieve the account information record
42   -{ok,Info}=CloudFiles:get_account_info().
43   -Info = #cf_account_info{bytes_used=360, container_count=3}
  38 +{ok,Info}=cferl_connection:get_account_info(CloudFiles).
  39 +Info = #cf_account_info{bytes_used=1735871382, container_count=5}
44 40
45 41 # Retrieve names of all existing containers (within the limits imposed by Cloud Files server)
46   -{ok,Names}=CloudFiles:get_containers_names().
  42 +{ok,Names}=cferl_connection:get_containers_names(CloudFiles).
47 43
48 44 # Retrieve names of a maximum of 3 existing containers
49   -{ok,ThreeNamesMax}=CloudFiles:get_containers_names(#cf_container_query_args{limit=3}).
  45 +{ok,ThreeNamesMax}=cferl_connection:get_containers_names(CloudFiles,#cf_container_query_args{limit=3}).
50 46
51 47 # Retrieve names of all containers currently CDN activated
52   -{ok,CurrentPublicNames}=CloudFiles:get_public_containers_names(active).
  48 +{ok,CurrentPublicNames}=cferl_connection:get_public_containers_names(CloudFiles,active).
53 49
54 50 # Retrieve names of all containers that are currently or have been CDN activated
55   -{ok,AllTimePublicNames}=CloudFiles:get_public_containers_names(all_time).
  51 +{ok,AllTimePublicNames}=cferl_connection:get_public_containers_names(CloudFiles,all_time).
56 52
57 53 # Retrieve details for all existing containers (within the server limits)
58   -{ok,ContainersDetails}=CloudFiles:get_containers_details().
  54 +{ok,ContainersDetails}=cferl_connection:get_containers_details(CloudFiles).
59 55
60 56 # ContainersDetails is a list of #cf_container_details records
61 57 [ContainerDetails|_]=ContainersDetails.
62   -ContainerDetails = #cf_container_details{name=<<"cferl-test">>, bytes=360, count=1}
  58 +ContainerDetails = #cf_container_details{name=<<".CDN_ACCESS_LOGS">>, bytes=261, count=1}
63 59
64 60 # Retrieve details for a maximum of 5 containers whose names start at cf
65   -{ok,CfContainersDetails}=CloudFiles:get_containers_details(#cf_container_query_args{marker=<<"cf">>,limit=5}).
  61 +{ok,CfContainersDetails}=cferl_connection:get_containers_details(CloudFiles,#cf_container_query_args{marker=<<"cf">>,limit=5}).
66 62
67 63 # Get a container reference by name
68   -{ok,Container}=CloudFiles:get_container(ContainerDetails#cf_container_details.name).
  64 +{ok,Container}=cferl_connection:get_container(CloudFiles,ContainerDetails#cf_container_details.name).
69 65
70 66 # Get container details from its reference
71   -ContainerName=Container:name().
72   -ContainerBytes=Container:bytes().
73   -ContainerSize=Container:count().
74   -ContainerIsEmpty=Container:is_empty().
  67 +ContainerName=cferl_container:name(Container).
  68 +ContainerBytes=cferl_container:bytes(Container).
  69 +ContainerSize=cferl_container:count(Container).
  70 +ContainerIsEmpty=cferl_container:is_empty(Container).
75 71
76   -# -> Name: <<"cferl-test">> - Bytes: 360 - Size: 1 - IsEmpty: false
  72 +# -> Name: <<".CDN_ACCESS_LOGS">> - Bytes: 261 - Size: 1 - IsEmpty: false
77 73
78 74 # Check a container's existence
79   -false=CloudFiles:container_exists(NewContainerName).
  75 +false=cferl_connection:container_exists(CloudFiles,NewContainerName).
80 76
81 77 # Create a new container
82   -{ok,NewContainer}=CloudFiles:create_container(NewContainerName).
  78 +{ok,NewContainer}=cferl_connection:create_container(CloudFiles,NewContainerName).
83 79
84   -true=CloudFiles:container_exists(NewContainerName).
  80 +true=cferl_connection:container_exists(CloudFiles,NewContainerName).
85 81
86 82 Check attributes of this newly created container
87   -NewContainerName=NewContainer:name().
88   -0=NewContainer:bytes().
89   -0=NewContainer:count().
90   -true=NewContainer:is_empty().
91   -false=NewContainer:is_public().
92   -<<>>=NewContainer:cdn_url().
93   -0=NewContainer:cdn_ttl().
94   -false=NewContainer:log_retention().
  83 +NewContainerName=cferl_container:name(NewContainer).
  84 +0=cferl_container:bytes(NewContainer).
  85 +0=cferl_container:count(NewContainer).
  86 +true=cferl_container:is_empty(NewContainer).
  87 +false=cferl_container:is_public(NewContainer).
  88 +<<>>=cferl_container:cdn_url(NewContainer).
  89 +0=cferl_container:cdn_ttl(NewContainer).
  90 +false=cferl_container:log_retention(NewContainer).
95 91
96 92 # Make the container public on the CDN (using the default TTL and ACLs)
97   -ok=NewContainer:make_public().
  93 +ok=cferl_container:make_public(CloudFiles,NewContainer).
98 94
99 95 # Activate log retention on the new container
100   -ok=NewContainer:set_log_retention(true).
  96 +ok=cferl_container:set_log_retention(CloudFiles,NewContainer,true).
101 97
102 98 # Refresh an existing container and check its attributes
103   -{ok,RefreshedContainer}=NewContainer:refresh().
104   -true=RefreshedContainer:is_public().
  99 +{ok,RefreshedContainer}=cferl_container:refresh(CloudFiles,NewContainer).
  100 +true=cferl_container:is_public(RefreshedContainer).
105 101
106   -io:format("~s~n~n",[RefreshedContainer:cdn_url()]).
107   -http://c0027258.cdn1.cloudfiles.rackspacecloud.com
  102 +io:format("~s~n~n",[cferl_container:cdn_url(RefreshedContainer)]).
  103 +http://05f98f987aa9393ccd8c-3d04f8822c5760cb271501bb0c358085.r17.cf1.rackcdn.com
108 104
109   -86400=RefreshedContainer:cdn_ttl().
110   -true=RefreshedContainer:log_retention().
  105 +86400=cferl_container:cdn_ttl(RefreshedContainer).
  106 +true=cferl_container:log_retention(RefreshedContainer).
111 107
112 108 ObjectName=<<"test.xml">>.
113 109 # Create an object *reference*, nothing is sent to the server yet
114   -{ok,Object}=RefreshedContainer:create_object(ObjectName).
  110 +{ok,Object}=cferl_container:create_object(CloudFiles,RefreshedContainer,ObjectName).
115 111 # As expected, it doesn't exist yet
116   -false=RefreshedContainer:object_exists(ObjectName).
  112 +false=cferl_container:object_exists(CloudFiles,RefreshedContainer,ObjectName).
117 113
118 114 # Write data in the object, which creates it on the server
119   -ok=Object:write_data(<<"<test/>">>,<<"application/xml">>).
  115 +ok=cferl_object:write_data(CloudFiles,Object,<<"<test/>">>,<<"application/xml">>).
120 116 # Now it exists!
121   -true=RefreshedContainer:object_exists(ObjectName).
  117 +true=cferl_container:object_exists(CloudFiles,RefreshedContainer,ObjectName).
122 118 # And trying to re-create it just returns it
123   -{ok,ExistingObject}=RefreshedContainer:create_object(ObjectName).
  119 +{ok,ExistingObject}=cferl_container:create_object(CloudFiles,RefreshedContainer,ObjectName).
124 120
125 121 # Set custom meta-data on it
126   -ok=Object:set_metadata([{<<"Key123">>,<<"my123Value">>}]).
  122 +ok=cferl_object:set_metadata(CloudFiles,Object,[{<<"Key123">>,<<"my123Value">>}]).
127 123
128 124 # An existing object can be accessed directly from its container
129   -{ok,GotObject}=RefreshedContainer:get_object(ObjectName).
  125 +{ok,GotObject}=cferl_container:get_object(CloudFiles,RefreshedContainer,ObjectName).
130 126
131 127 # Object names and details can be queried
132   -{ok,[ObjectName]}=RefreshedContainer:get_objects_names().
133   -{ok,[ObjectName]}=RefreshedContainer:get_objects_names(#cf_object_query_args{limit=1}).
134   -{ok,[ObjectDetails]}=RefreshedContainer:get_objects_details().
135   -ObjectDetails = #cf_object_details{name=<<"test.xml">>, bytes=8, last_modified={{2010,10,14},{15,49,22}}, content_type=application/xml, etag=4366c359d1a7b9b248fa262775613699}
  128 +{ok,[ObjectName]}=cferl_container:get_objects_names(CloudFiles,RefreshedContainer).
  129 +{ok,[ObjectName]}=cferl_container:get_objects_names(CloudFiles,RefreshedContainer,#cf_object_query_args{limit=1}).
  130 +{ok,[ObjectDetails]}=cferl_container:get_objects_details(CloudFiles,RefreshedContainer).
  131 +ObjectDetails = #cf_object_details{name=<<"test.xml">>, bytes=8, last_modified={{2013,3,16},{0,8,47}}, content_type=application/xml, etag=4366c359d1a7b9b248fa262775613699}
136 132
137 133 # Read the whole data
138   -{ok,<<"<test/>">>}=Object:read_data().
  134 +{ok,<<"<test/>">>}=cferl_object:read_data(CloudFiles,Object).
139 135 # Read the data with an offset and a size
140   -{ok,<<"test">>}=Object:read_data(1,4).
  136 +{ok,<<"test">>}=cferl_object:read_data(CloudFiles,Object,1,4).
141 137
142 138 # Refresh the object so its attributes and metadata are up to date
143   -{ok,RefreshedObject}=Object:refresh().
  139 +{ok,RefreshedObject}=cferl_object:refresh(CloudFiles,Object).
144 140
145 141 # Get object attributes
146   -ObjectName=RefreshedObject:name().
147   -8=RefreshedObject:bytes().
148   -{{D,M,Y},{H,Mi,S}}=RefreshedObject:last_modified().
149   -<<"application/xml;charset=UTF-8">>=RefreshedObject:content_type().
150   -Etag=RefreshedObject:etag().
  142 +ObjectName=cferl_object:name(RefreshedObject).
  143 +8=cferl_object:bytes(RefreshedObject).
  144 +{{D,M,Y},{H,Mi,S}}=cferl_object:last_modified(RefreshedObject).
  145 +<<"application/xml">>=cferl_object:content_type(RefreshedObject).
  146 +Etag=cferl_object:etag(RefreshedObject).
151 147
152 148 # Get custom meta-data
153   -[{<<"Key123">>,<<"my123Value">>}]=RefreshedObject:metadata().
  149 +[{<<"Key123">>,<<"my123Value">>}]=cferl_object:metadata(RefreshedObject).
154 150
155 151 # Delete the object
156   -ok=RefreshedObject:delete().
  152 +ok=cferl_object:delete(CloudFiles,RefreshedObject).
157 153
158 154 # Data can be streamed to the server from a generating function
159   -{ok,StreamedObject}=RefreshedContainer:create_object(<<"streamed.txt">>).
160   -StreamedObject:write_data_stream(WriteDataFun,<<"text/plain">>,1000).
  155 +{ok,StreamedObject}=cferl_container:create_object(CloudFiles,RefreshedContainer,<<"streamed.txt">>).
  156 +cferl_object:write_data_stream(CloudFiles,StreamedObject,WriteDataFun,<<"text/plain">>,1000).
161 157
162 158 # Data can be streamed from the server to a receiving function
163   -ok=StreamedObject:read_data_stream(ReadDataFun).
  159 +ok=cferl_object:read_data_stream(CloudFiles,StreamedObject,ReadDataFun).
164 160
165 161 # Create all the directory elements for a particular object path
166   -ok=RefreshedContainer:ensure_dir(<<"photos/plants/fern.jpg">>).
167   -true=RefreshedContainer:object_exists(<<"photos">>).
168   -true=RefreshedContainer:object_exists(<<"photos/plants">>).
  162 +ok=cferl_container:ensure_dir(CloudFiles,RefreshedContainer,<<"photos/plants/fern.jpg">>).
  163 +true=cferl_container:object_exists(CloudFiles,RefreshedContainer,<<"photos">>).
  164 +true=cferl_container:object_exists(CloudFiles,RefreshedContainer,<<"photos/plants">>).
169 165
170 166 # Make the container private
171   -ok=RefreshedContainer:make_private().
  167 +ok=cferl_container:make_private(CloudFiles,RefreshedContainer).
172 168
173 169 # Delete an existing container (must be empty)
174   -ok=RefreshedContainer:delete().
175   -```
  170 +ok=cferl_container:delete(CloudFiles,RefreshedContainer).
  171 +```
176 172
177   -More information
178   -----------------
  173 +## More information
179 174
180 175 Read the Rackspace Cloud Files API specification: <http://www.rackspacecloud.com/cloud_hosting_products/files/api>
181 176
182 177 Contact the author: <david@dossot.net>
183 178
  179 +## Copyright (c) 2010-2013 David Dossot - MIT License
29 include/cferl.hrl
... ... @@ -1,24 +1,47 @@
1 1 %%%
2 2 %%% @doc Rackspace Cloud Files Erlang Client
3 3 %%% @author David Dossot <david@dossot.net>
  4 +%%% @author Tilman Holschuh <tilman.holschuh@gmail.com>
4 5 %%%
5 6 %%% See LICENSE for license information.
6 7 %%% Copyright (c) 2010 David Dossot
7 8 %%%
8 9
9   --define(API_BASE_URL, "auth.api.rackspacecloud.com").
  10 +-define(US_API_BASE_URL, "identity.api.rackspacecloud.com").
  11 +-define(UK_API_BASE_URL, "lon.identity.api.rackspacecloud.com").
10 12 -define(VERSION_PATH, "/v1.0").
11 13
12 14 -define(DEFAULT_REQUEST_TIMEOUT, 30000).
13 15 -define(OBJECT_META_HEADER_PREFIX, "X-Object-Meta-").
14 16 -define(DIRECTORY_OBJECT_CONTENT_TYPE, <<"application/directory">>).
15 17
  18 +-define(IS_CONNECTION(C), is_record(C, cf_connection)).
  19 +-define(IS_CONTAINER(C), is_record(C, cf_container)).
  20 +-define(IS_OBJECT(O), is_record(O, cf_object)).
  21 +
  22 +-record(cf_connection, {version :: string(),
  23 + auth_token :: string(),
  24 + storage_url :: string(),
  25 + cdn_management_url :: string()
  26 + }).
  27 +
16 28 -record(cf_account_info, {bytes_used, container_count}).
17 29
18   --record(cf_container_query_args, {marker, limit}).
19 30 -record(cf_container_details, {name, bytes, count}).
  31 +-record(cf_container, {container_details :: #cf_container_details{},
  32 + container_path :: string(),
  33 + cdn_details :: [{atom(), term()}]
  34 + }).
  35 +
  36 +-record(cf_container_query_args, {marker, limit}).
20 37 -record(cf_container_cdn_config, {ttl = 86400, user_agent_acl, referrer_acl}).
21 38
22   --record(cf_object_query_args, {marker, limit, prefix, path}).
23 39 -record(cf_object_details, {name, bytes = 0, last_modified, content_type, etag}).
  40 +-record(cf_object, {container :: #cf_container{},
  41 + object_details :: #cf_object_details{},
  42 + object_path :: string(),
  43 + http_headers :: [{string(), string()}]
  44 + }).
  45 +
  46 +-record(cf_object_query_args, {marker, limit, prefix, path}).
24 47
2  int_tests
... ... @@ -1,4 +1,4 @@
1 1 #!/bin/sh
2   -./rebar get-deps clean compile eunit
  2 +rebar get-deps clean compile eunit
3 3 erl -boot start_sasl -config test/elog -pa .eunit -pa ebin -pa deps/ibrowse/ebin -s cferl_integration_tests -noshell
4 4
4 rebar.config
... ... @@ -1,7 +1,7 @@
1   -{erl_opts, [{src_dirs, ["src", "lib"]}]}.
  1 +{erl_opts, [debug_info, {src_dirs, ["src", "lib"]}]}.
2 2
3 3 {edoc_opts, [{application, ["cferl"]}]}.
4 4
5 5 {deps_dir, ["deps"]}.
6 6
7   -{deps, [{ibrowse, ".*", {git, "git://github.com/cmullaparthi/ibrowse.git", {branch, master}}}]}.
  7 +{deps, [{ibrowse, ".*", {git, "git://github.com/cmullaparthi/ibrowse.git", {tag, "v4.0.1"}}}]}.
2  src/cferl.app.src
... ... @@ -1,7 +1,7 @@
1 1 {application,
2 2 cferl,
3 3 [{description, "Rackspace Cloud Files client application"},
4   - {vsn, "1.3.2"},
  4 + {vsn, "2.0.0"},
5 5 {modules, [
6 6 cferl,
7 7 cferl_connection,
57 src/cferl.erl
... ... @@ -1,12 +1,11 @@
1 1 %%%
2 2 %%% @doc Authentication and connection with Rackspace Cloud Files.
3 3 %%% @author David Dossot <david@dossot.net>
  4 +%%% @author Tilman Holschuh <tilman.holschuh@gmail.com>
4 5 %%%
5 6 %%% See LICENSE for license information.
6 7 %%% Copyright (c) 2010 David Dossot
7 8 %%%
8   -%%% @type cferl_connection() = term(). Reference to the cferl_connection parameterized module.
9   -%%% @type cferl_error() = {error, not_found} | {error, unauthorized} | {error, {unexpected_response, Other}}.
10 9
11 10 -module(cferl).
12 11 -author('David Dossot <david@dossot.net>').
@@ -15,30 +14,28 @@
15 14 -export([connect/2, connect/3]).
16 15 -define(APPLICATION, cferl).
17 16
18   -%% @doc Authenticate and open connection.
19   -%% @spec connect(Username, ApiKey) -> {ok, CloudFiles} | Error
20   -%% Username = string() | binary()
21   -%% ApiKey = string() | binary()
22   -%% CloudFiles = cferl_connection()
23   -%% Error = cferl_error()
  17 +-type(username() :: string() | binary()).
  18 +-type(api_key() :: string() | binary()).
  19 +-type(auth_service() :: string() | binary() | us | uk).
  20 +
  21 +%% @doc Authenticate and open connection (US).
  22 +-spec connect(username(), api_key()) -> {ok, #cf_connection{}} | cferl_lib:cferl_error().
24 23 connect(Username, ApiKey) when is_binary(Username), is_binary(ApiKey) ->
25 24 connect(binary_to_list(Username),
26 25 binary_to_list(ApiKey));
27 26 connect(Username, ApiKey) when is_list(Username), is_list(ApiKey) ->
28   - AuthUrl = "https://" ++ ?API_BASE_URL ++ ":443" ++ ?VERSION_PATH,
29   - connect(Username, ApiKey, AuthUrl).
  27 + connect(Username, ApiKey, us).
30 28
31 29 %% @doc Authenticate and open connection.
32   -%% @spec connect(Username, ApiKey, AuthUrl) -> {ok, CloudFiles} | Error
33   -%% Username = string() | binary()
34   -%% ApiKey = string() | binary()
35   -%% AuthUrl = string() | binary()
36   -%% CloudFiles = cferl_connection()
37   -%% Error = cferl_error()
  30 +-spec connect(username(), api_key(), auth_service()) -> {ok, #cf_connection{}} | cferl_lib:cferl_error().
  31 +connect(Username, ApiKey, us) ->
  32 + AuthUrl = "https://" ++ ?US_API_BASE_URL ++ ":443" ++ ?VERSION_PATH,
  33 + connect(Username, ApiKey, AuthUrl);
  34 +connect(Username, ApiKey, uk) ->
  35 + AuthUrl = "https://" ++ ?UK_API_BASE_URL ++ ":443" ++ ?VERSION_PATH,
  36 + connect(Username, ApiKey, AuthUrl);
38 37 connect(Username, ApiKey, AuthUrl) when is_binary(Username), is_binary(ApiKey), is_binary(AuthUrl) ->
39   - connect(binary_to_list(Username),
40   - binary_to_list(ApiKey),
41   - binary_to_list(AuthUrl));
  38 + connect(binary_to_list(Username), binary_to_list(ApiKey), binary_to_list(AuthUrl));
42 39 connect(Username, ApiKey, AuthUrl) when is_list(Username), is_list(ApiKey), is_list(AuthUrl) ->
43 40 ensure_started(),
44 41
@@ -53,16 +50,18 @@ connect(Username, ApiKey, AuthUrl) when is_list(Username), is_list(ApiKey), is_l
53 50
54 51 %% @doc Ensure started for the sake of verifying that required applications are running.
55 52 ensure_started() ->
56   - ensure_started(
57   - lists:any(fun({Application, _Description, _Vsn}) ->
58   - Application == ?APPLICATION
59   - end,
60   - application:which_applications())).
61   -
62   -ensure_started(true) ->
63   - ok;
64   -ensure_started(false) ->
65   - application:start(?APPLICATION).
  53 + ensure_started(?APPLICATION).
  54 +
  55 +ensure_started(App) ->
  56 + ensure_started(App, application:start(App)).
  57 +
  58 +ensure_started(_App, ok ) -> ok;
  59 +ensure_started(_App, {error, {already_started, _App}}) -> ok;
  60 +ensure_started(App, {error, {not_started, Dep}}) ->
  61 + ok = ensure_started(Dep),
  62 + ensure_started(App);
  63 +ensure_started(App, {error, Reason}) ->
  64 + erlang:error({app_start_failed, App, Reason}).
66 65
67 66 connect_result({ok, "204", ResponseHeaders, _ResponseBody}) ->
68 67 {ok, Version} = application:get_key(?APPLICATION, vsn),
188 src/cferl_connection.erl
@@ -11,28 +11,36 @@
11 11 %%% @type cf_container_details() = record(). Record of type cf_container_details.
12 12 %%% @type cferl_container() = term(). Reference to the cferl_container parameterized module.
13 13
14   --module(cferl_connection, [Version, AuthToken, StorageUrl, CdnManagementUrl]).
  14 +-module(cferl_connection).
15 15 -author('David Dossot <david@dossot.net>').
16 16 -include("cferl.hrl").
17 17
18 18 %% Public API
19   --export([get_account_info/0,
20   - get_containers_names/0, get_containers_names/1,
21   - get_containers_details/0, get_containers_details/1,
22   - container_exists/1, get_container/1, create_container/1, delete_container/1,
23   - get_public_containers_names/1]).
  19 +-export([get_account_info/1,
  20 + get_containers_names/1,
  21 + get_containers_names/2,
  22 + get_containers_details/1,
  23 + get_containers_details/2,
  24 + container_exists/2,
  25 + get_container/2,
  26 + create_container/2,
  27 + delete_container/2,
  28 + get_public_containers_names/2]).
24 29
25 30 %% Exposed for internal usage
26   --export([send_storage_request/3, send_storage_request/4, send_storage_request/5,
27   - send_cdn_management_request/3, send_cdn_management_request/4,
  31 +-export([new/4,
  32 + send_storage_request/4,
  33 + send_storage_request/5,
  34 + send_storage_request/6,
  35 + send_cdn_management_request/4,
  36 + send_cdn_management_request/5,
28 37 async_response_loop/1]).
29 38
  39 +
30 40 %% @doc Retrieve the account information.
31   -%% @spec get_account_info() -> {ok, AccountInfo} | Error
32   -%% AccountInfo = cf_account_info()
33   -%% Error = cferl_error()
34   -get_account_info() ->
35   - Result = send_storage_request(head, "", raw),
  41 +-spec get_account_info(#cf_connection{}) -> {ok, #cf_account_info{}} | cferl_lib:cferl_error().
  42 +get_account_info(Conn) when ?IS_CONNECTION(Conn) ->
  43 + Result = send_storage_request(Conn, head, "", raw),
36 44 get_account_info_result(Result).
37 45
38 46 get_account_info_result({ok, "204", ResponseHeaders, _}) ->
@@ -47,20 +55,17 @@ get_account_info_result(Other) ->
47 55 cferl_lib:error_result(Other).
48 56
49 57 %% @doc Retrieve all the containers names (within the limits imposed by Cloud Files server).
50   -%% @spec get_containers_names() -> {ok, [binary()]} | Error
51   -%% Error = cferl_error()
52   -get_containers_names() ->
53   - Result = send_storage_request(get, "", raw),
  58 +-spec get_containers_names(#cf_connection{}) -> {ok, [binary()]} | cferl_lib:cferl_error().
  59 +get_containers_names(Conn) when ?IS_CONNECTION(Conn) ->
  60 + Result = send_storage_request(Conn, get, "", raw),
54 61 get_containers_names_result(Result).
55 62
56 63 %% @doc Retrieve the containers names filtered by the provided query arguments.
57 64 %% If you supply the optional limit and marker arguments, the call will return the number of containers specified in limit, starting after the object named in marker.
58   -%% @spec get_containers_names(QueryArgs) -> {ok, [binary()]} | Error
59   -%% QueryArgs = cf_container_query_args()
60   -%% Error = cferl_error()
61   -get_containers_names(QueryArgs) when is_record(QueryArgs, cf_container_query_args) ->
  65 +-spec get_containers_names(#cf_connection{}, #cf_container_query_args{}) -> {ok, [binary()]} | cferl_lib:cferl_error().
  66 +get_containers_names(Conn, QueryArgs) when ?IS_CONNECTION(Conn), is_record(QueryArgs, cf_container_query_args) ->
62 67 QueryString = cferl_lib:container_query_args_to_string(QueryArgs),
63   - Result = send_storage_request(get, QueryString, raw),
  68 + Result = send_storage_request(Conn, get, QueryString, raw),
64 69 get_containers_names_result(Result).
65 70
66 71 get_containers_names_result({ok, "204", _, _}) ->
@@ -71,18 +76,16 @@ get_containers_names_result(Other) ->
71 76 cferl_lib:error_result(Other).
72 77
73 78 %% @doc Retrieve all the containers information (within the limits imposed by Cloud Files server).
74   -%% @spec get_containers_details() -> {ok, [cf_container_details()]} | Error
75   -%% Error = cferl_error()
76   -get_containers_details() ->
77   - get_containers_details(#cf_container_query_args{}).
  79 +-spec get_containers_details(#cf_connection{}) -> {ok, [#cf_container_details{}]} | cferl_lib:cferl_error().
  80 +get_containers_details(Conn) when ?IS_CONNECTION(Conn) ->
  81 + get_containers_details(Conn, #cf_container_query_args{}).
78 82
79 83 %% @doc Retrieve the containers information filtered by the provided query arguments.
80   -%% @spec get_containers_details(QueryArgs) -> {ok, [cf_container_details()]} | Error
81   -%% QueryArgs = cf_container_query_args()
82   -%% Error = cferl_error()
83   -get_containers_details(QueryArgs) when is_record(QueryArgs, cf_container_query_args) ->
  84 +-spec get_containers_details(#cf_connection{}, #cf_container_query_args{}) ->
  85 + {ok, [#cf_container_details{}]} | cferl_lib:cferl_error().
  86 +get_containers_details(Conn, QueryArgs) when ?IS_CONNECTION(Conn), is_record(QueryArgs, cf_container_query_args) ->
84 87 QueryString = cferl_lib:container_query_args_to_string(QueryArgs),
85   - Result = send_storage_request(get, QueryString, json),
  88 + Result = send_storage_request(Conn, get, QueryString, json),
86 89 get_containers_details_result(Result).
87 90
88 91 get_containers_details_result({ok, "204", _, _}) ->
@@ -104,9 +107,9 @@ get_containers_details_result(Other) ->
104 107 cferl_lib:error_result(Other).
105 108
106 109 %% @doc Test the existence of a container.
107   -%% @spec container_exists(Name::binary()) -> true | false
108   -container_exists(Name) when is_binary(Name) ->
109   - Result = send_storage_request(head, get_container_path(Name), raw),
  110 +-spec container_exists(#cf_connection{}, Name::binary()) -> true | false.
  111 +container_exists(Conn, Name) when ?IS_CONNECTION(Conn), is_binary(Name) ->
  112 + Result = send_storage_request(Conn, head, get_container_path(Name), raw),
110 113 container_exists_result(Result).
111 114
112 115 container_exists_result({ok, "204", _, _}) ->
@@ -115,26 +118,24 @@ container_exists_result(_) ->
115 118 false.
116 119
117 120 %% @doc Get a reference to an existing container.
118   -%% @spec get_container(Name::binary) -> {ok, Container} | Error
119   -%% Container = cferl_container()
120   -%% Error = cferl_error()
121   -get_container(Name) when is_binary(Name) ->
122   - Result = send_storage_request(head, get_container_path(Name), raw),
123   - get_container_result(Name, Result).
124   -
125   -get_container_result(Name, {ok, "204", ResponseHeaders, _}) ->
  121 +-spec get_container(#cf_connection{}, Name::binary()) -> {ok, #cf_container{}} | cferl_lib:cferl_error().
  122 +get_container(Conn, Name) when ?IS_CONNECTION(Conn), is_binary(Name) ->
  123 + Result = send_storage_request(Conn, head, get_container_path(Name), raw),
  124 + get_container_result(Conn, Name, Result).
  125 +
  126 +get_container_result(Conn, Name, {ok, "204", ResponseHeaders, _}) ->
126 127 ContainerDetails = #cf_container_details{
127 128 name = Name,
128 129 bytes = cferl_lib:get_int_header("x-container-bytes-used", ResponseHeaders),
129 130 count = cferl_lib:get_int_header("x-container-object-count", ResponseHeaders)
130 131 },
131   - {ok, CdnDetails} = get_container_cdn_details(Name),
132   - {ok, cferl_container:new(THIS, ContainerDetails, get_container_path(Name), CdnDetails)};
133   -get_container_result(_, Other) ->
  132 + {ok, CdnDetails} = get_container_cdn_details(Conn, Name),
  133 + {ok, cferl_container:new(ContainerDetails, get_container_path(Name), CdnDetails)};
  134 +get_container_result(_, _, Other) ->
134 135 cferl_lib:error_result(Other).
135 136
136   -get_container_cdn_details(Name) ->
137   - Result = send_cdn_management_request(head, get_container_path(Name), raw),
  137 +get_container_cdn_details(Conn, Name) ->
  138 + Result = send_cdn_management_request(Conn, head, get_container_path(Name), raw),
138 139 get_container_cdn_details_result(Result).
139 140
140 141 get_container_cdn_details_result({ok, "204", ResponseHeaders, _}) ->
@@ -153,25 +154,24 @@ build_cdn_details_proplist(Headers) ->
153 154 ].
154 155
155 156 %% @doc Create a new container (name must not be already used).
156   -%% @spec create_container(Name::binary) -> {ok, Container} | Error
157   -%% Container = cferl_container()
158   -%% Error = {error, already_existing} | cferl_error()
159   -create_container(Name) when is_binary(Name) ->
160   - Result = send_storage_request(put, get_container_path(Name), raw),
161   - create_container_result(Name, Result).
162   -
163   -create_container_result(Name, {ok, "201", _, _}) ->
164   - get_container(Name);
165   -create_container_result(_, {ok, "202", _, _}) ->
  157 +-spec create_container(#cf_connection{}, Name::binary()) ->
  158 + {ok, #cf_container{}} | {error, already_existing} | cferl_lib:cferl_error().
  159 +create_container(Conn, Name) when ?IS_CONNECTION(Conn), is_binary(Name) ->
  160 + Result = send_storage_request(Conn, put, get_container_path(Name), raw),
  161 + create_container_result(Conn, Name, Result).
  162 +
  163 +create_container_result(Conn, Name, {ok, "201", _, _}) ->
  164 + get_container(Conn, Name);
  165 +create_container_result(_, _, {ok, "202", _, _}) ->
166 166 {error, already_existing};
167   -create_container_result(_, Other) ->
  167 +create_container_result(_, _, Other) ->
168 168 cferl_lib:error_result(Other).
169 169
170 170 %% @doc Delete a container (which must be empty).
171   -%% @spec delete_container(Name::binary) -> ok | Error
  171 +-spec delete_container(#cf_connection{}, Name::binary()) -> ok | {error, not_empty} | cferl_lib:cferl_error().
172 172 %% Error = {error, not_empty} | cferl_error()
173   -delete_container(Name) when is_binary(Name) ->
174   - Result = send_storage_request(delete, get_container_path(Name), raw),
  173 +delete_container(Conn, Name) when ?IS_CONNECTION(Conn), is_binary(Name) ->
  174 + Result = send_storage_request(Conn, delete, get_container_path(Name), raw),
175 175 delete_container_result(Result).
176 176
177 177 delete_container_result({ok, "204", _, _}) ->
@@ -182,13 +182,12 @@ delete_container_result(Other) ->
182 182 cferl_lib:error_result(Other).
183 183
184 184 %% @doc Retrieve the names of public (CDN-enabled) containers, whether they are still public (active) or happen to have been exposed in the past(all_time).
185   -%% @spec get_public_containers_names(TimeFilter::active | all_time) -> {ok, [binary()]} | Error
186   -%% Error = cferl_error()
187   -get_public_containers_names(active) ->
188   - Result = send_cdn_management_request(get, "?enabled_only=true", raw),
  185 +-spec get_public_containers_names(#cf_connection{}, TimeFilter::active | all_time) -> {ok, [binary()]} | cferl_lib:cferl_error().
  186 +get_public_containers_names(Conn, active) when ?IS_CONNECTION(Conn) ->
  187 + Result = send_cdn_management_request(Conn, get, "?enabled_only=true", raw),
189 188 get_public_containers_names_result(Result);
190   -get_public_containers_names(all_time) ->
191   - Result = send_cdn_management_request(get, "", raw),
  189 +get_public_containers_names(Conn, all_time) when ?IS_CONNECTION(Conn) ->
  190 + Result = send_cdn_management_request(Conn, get, "", raw),
192 191 get_public_containers_names_result(Result).
193 192
194 193 get_public_containers_names_result({ok, "204", _, _}) ->
@@ -200,34 +199,58 @@ get_public_containers_names_result(Other) ->
200 199
201 200 %% Friend functions
202 201 %% @hidden
203   -send_storage_request(Method, PathAndQuery, Accept)
  202 +-spec new(Version::string(),
  203 + AuthToken :: string(),
  204 + StorageUrl :: string(),
  205 + CdnManagementUrl :: string()) -> #cf_connection{}.
  206 +new(Version, AuthToken, StorageUrl, CdnManagementUrl) ->
  207 + #cf_connection{
  208 + version = Version,
  209 + auth_token = AuthToken,
  210 + storage_url = StorageUrl,
  211 + cdn_management_url = CdnManagementUrl }.
  212 +
  213 +%% @hidden
  214 +send_storage_request(Connection, Method, PathAndQuery, Accept)
204 215 when is_atom(Method),
205 216 is_atom(Accept) or is_function(Accept, 1) ->
206 217
207   - send_storage_request(Method, PathAndQuery, [], Accept).
  218 + send_storage_request(Connection, Method, PathAndQuery, [], Accept).
208 219
209   -send_storage_request(Method, PathAndQuery, Headers, Accept)
  220 +send_storage_request(Connection, Method, PathAndQuery, Headers, Accept)
210 221 when is_atom(Method), is_list(Headers),
211 222 is_atom(Accept) or is_function(Accept, 1) ->
212 223
213   - send_request(StorageUrl, Method, PathAndQuery, Headers, <<>>, Accept).
  224 + send_request(Connection#cf_connection.storage_url,
  225 + Method, PathAndQuery,
  226 + handle_headers(Connection, Headers),
  227 + <<>>, Accept).
214 228
215   -send_storage_request(Method, PathAndQuery, Headers, Body, Accept)
  229 +send_storage_request(Connection, Method, PathAndQuery, Headers, Body, Accept)
216 230 when is_atom(Method), is_list(Headers),
217 231 is_binary(Body) or is_function(Body, 0),
218 232 is_atom(Accept) or is_function(Accept, 1) ->
219 233
220   - send_request(StorageUrl, Method, PathAndQuery, Headers, Body, Accept).
  234 + send_request(Connection#cf_connection.storage_url,
  235 + Method, PathAndQuery,
  236 + handle_headers(Connection, Headers),
  237 + Body, Accept).
221 238
222 239 %% @hidden
223   -send_cdn_management_request(Method, PathAndQuery, Accept)
  240 +send_cdn_management_request(Connection, Method, PathAndQuery, Accept)
224 241 when is_atom(Method), is_atom(Accept) ->
225   - send_request(CdnManagementUrl, Method, PathAndQuery, [], <<>>, Accept).
  242 + send_request(Connection#cf_connection.cdn_management_url,
  243 + Method, PathAndQuery,
  244 + handle_headers(Connection, []),
  245 + <<>>, Accept).
226 246
227 247 %% @hidden
228   -send_cdn_management_request(Method, PathAndQuery, Headers, Accept)
  248 +send_cdn_management_request(Connection, Method, PathAndQuery, Headers, Accept)
229 249 when is_atom(Method), is_list(Headers), is_atom(Accept) ->
230   - send_request(CdnManagementUrl, Method, PathAndQuery, Headers, <<>>, Accept).
  250 + send_request(Connection#cf_connection.cdn_management_url,
  251 + Method, PathAndQuery,
  252 + handle_headers(Connection, Headers),
  253 + <<>>, Accept).
231 254
232 255 %% @hidden
233 256 get_container_path(Name) when is_binary(Name) ->
@@ -245,7 +268,7 @@ send_request(BaseUrl, Method, PathAndQuery, Headers, Body, ResultFun)
245 268 is_binary(Body) or is_function(Body, 0),
246 269 is_function(ResultFun, 1) ->
247 270
248   - ResultPid = spawn(fun() -> async_response_loop(ResultFun) end),
  271 + ResultPid = proc_lib:spawn(fun() -> async_response_loop(ResultFun) end),
249 272 Options = [{stream_to, {ResultPid, once}}],
250 273 do_send_request(BaseUrl, Method, PathAndQuery, Headers, Body, Options);
251 274
@@ -272,13 +295,16 @@ do_send_request(BaseUrl, Method, PathAndQuery, Headers, Body, Options)
272 295 is_list(Options) ->
273 296
274 297 ibrowse:send_req(BaseUrl ++ PathAndQuery,
275   - [{"User-Agent", "cferl (CloudFiles Erlang API) v" ++ Version},
276   - {"X-Auth-Token", AuthToken} |
277   - cferl_lib:binary_headers_to_string(Headers)],
  298 + cferl_lib:binary_headers_to_string(Headers),
278 299 Method,
279 300 Body,
280 301 [{response_format, binary} | Options]).
281 302
  303 +handle_headers(Connection, Headers) ->
  304 + [{"User-Agent", "cferl (CloudFiles Erlang API) v" ++ Connection#cf_connection.version},
  305 + {"X-Auth-Token", Connection#cf_connection.auth_token}
  306 + | Headers].
  307 +
282 308 build_json_query_string(PathAndQuery) when is_list(PathAndQuery) ->
283 309 PathAndQuery ++
284 310 case lists:member($?, PathAndQuery) of
241 src/cferl_container.erl
@@ -5,88 +5,113 @@
5 5 %%% See LICENSE for license information.
6 6 %%% Copyright (c) 2010 David Dossot
7 7 %%%
8   -%%% @type cferl_error() = {error, not_found} | {error, unauthorized} | {error, {unexpected_response, Other}}.
9 8 %%% @type cf_container_cdn_config() = record(). Record of type cf_container_cdn_config.
10 9 %%% @type cf_object_query_args() = record(). Record of type cf_object_query_args.
11 10 %%% @type cf_object_details() = record(). Record of type cf_object_details.
12   -%%% @type cferl_object() = term(). Reference to the cferl_object parameterized module.
13 11
14   --module(cferl_container, [Connection, ContainerDetails, ContainerPath, CdnDetails]).
  12 +-module(cferl_container).
15 13 -author('David Dossot <david@dossot.net>').
16 14 -include("cferl.hrl").
17 15
18 16 %% Public API
19   --export([name/0, bytes/0, count/0, is_empty/0, is_public/0, cdn_url/0, cdn_ttl/0, log_retention/0,
20   - make_public/0, make_public/1, make_private/0, set_log_retention/1,
21   - refresh/0, delete/0,
22   - get_objects_names/0, get_objects_names/1, get_objects_details/0, get_objects_details/1,
23   - object_exists/1, get_object/1, create_object/1, delete_object/1,
24   - ensure_dir/1]).
  17 +-export([name/1,
  18 + bytes/1,
  19 + count/1,
  20 + is_empty/1,
  21 + is_public/1,
  22 + cdn_url/1,
  23 + cdn_ttl/1,
  24 + log_retention/1,
  25 + make_public/2,
  26 + make_public/3,
  27 + make_private/2,
  28 + set_log_retention/3,
  29 + refresh/2,
  30 + delete/2,
  31 + get_objects_names/2,
  32 + get_objects_names/3,
  33 + get_objects_details/2,
  34 + get_objects_details/3,
  35 + object_exists/3,
  36 + get_object/3,
  37 + create_object/3,
  38 + delete_object/3,
  39 + ensure_dir/3
  40 + ]).
  41 +
  42 +%% Exposed for internal usage
  43 +-export([new/3 ]).
  44 +
25 45
26 46 %% @doc Name of the current container.
27   -%% @spec name() -> binary()
28   -name() ->
  47 +-spec name(#cf_container{}) -> binary().
  48 +name(Container) when ?IS_CONTAINER(Container) ->
  49 + ContainerDetails = Container#cf_container.container_details,
29 50 ContainerDetails#cf_container_details.name.
30 51
31 52 %% @doc Size in bytes of the current container.
32   -%% @spec bytes() -> integer()
33   -bytes() ->
  53 +-spec bytes(#cf_container{}) -> integer().
  54 +bytes(Container) when ?IS_CONTAINER(Container) ->
  55 + ContainerDetails = Container#cf_container.container_details,
34 56 ContainerDetails#cf_container_details.bytes.
35 57
36 58 %% @doc Number of objects in the current container.
37   -%% @spec count() -> integer()
38   -count() ->
  59 +-spec count(#cf_container{}) -> integer().
  60 +count(Container) when ?IS_CONTAINER(Container) ->
  61 + ContainerDetails = Container#cf_container.container_details,
39 62 ContainerDetails#cf_container_details.count.
40 63
41 64 %% @doc Determine if the current container is empty.
42   -%% @spec is_empty() -> true | false
43   -is_empty() ->
44   - count() == 0.
  65 +-spec is_empty(#cf_container{}) -> true | false.
  66 +is_empty(Container) ->
  67 + count(Container) == 0.
45 68
46 69 %% @doc Determine if the current container is public (CDN-enabled).
47   -%% @spec is_public() -> true | false
48   -is_public() ->
  70 +-spec is_public(#cf_container{}) -> true | false.
  71 +is_public(Container) when ?IS_CONTAINER(Container) ->
  72 + CdnDetails = Container#cf_container.cdn_details,
49 73 proplists:get_value(cdn_enabled, CdnDetails).
50 74
51 75 %% @doc CDN of the container URL, if it is public.
52   -%% @spec cdn_url() -> binary()
53   -cdn_url() ->
  76 +-spec cdn_url(#cf_container{}) -> binary().
  77 +cdn_url(Container) when ?IS_CONTAINER(Container) ->
  78 + CdnDetails = Container#cf_container.cdn_details,
54 79 proplists:get_value(cdn_uri, CdnDetails).
55 80
56 81 %% @doc TTL (in seconds) of the container, if it is public.
57   -%% @spec cdn_ttl() -> integer()
58   -cdn_ttl() ->
  82 +-spec cdn_ttl(#cf_container{}) -> integer().
  83 +cdn_ttl(Container) when ?IS_CONTAINER(Container) ->
  84 + CdnDetails = Container#cf_container.cdn_details,
59 85 proplists:get_value(ttl, CdnDetails).
60 86
61 87 %% @doc Determine if log retention is enabled on this container (which must be public).
62   -%% @spec log_retention() -> true | false
63   -log_retention() ->
64   - is_public() andalso proplists:get_value(log_retention, CdnDetails).
  88 +-spec log_retention(#cf_container{}) -> true | false.
  89 +log_retention(Container) when ?IS_CONTAINER(Container) ->
  90 + CdnDetails = Container#cf_container.cdn_details,
  91 + is_public(Container) andalso proplists:get_value(log_retention, CdnDetails).
65 92
66 93 %% @doc Make the current container publicly accessible on CDN, using the default configuration (ttl of 1 day and no ACL).
67   -%% @spec make_public() -> ok | Error
68   -%% Error = cferl_error()
69   -make_public() ->
70   - make_public(#cf_container_cdn_config{}).
  94 +-spec make_public(#cf_connection{}, #cf_container{}) -> ok | cferl_lib:cferl_error().
  95 +make_public(Connection, Container) when ?IS_CONNECTION(Connection), ?IS_CONTAINER(Container) ->
  96 + make_public(Connection, Container, #cf_container_cdn_config{}).
71 97
72 98 %% @doc Make the current container publicly accessible on CDN, using the provided configuration.
73 99 %% ttl is in seconds.
74 100 %% user_agent_acl and referrer_acl are Perl-compatible regular expression used to limit access to this container.
75   -%% @spec make_public(CdnConfig) -> ok | Error
76   -%% CdnConfig = cf_container_cdn_config()
77   -%% Error = cferl_error()
78   -make_public(CdnConfig) when is_record(CdnConfig, cf_container_cdn_config) ->
79   - PutResult = Connection:send_cdn_management_request(put, ContainerPath, raw),
80   - make_public_put_result(CdnConfig, PutResult).
  101 +-spec make_public(#cf_connection{}, #cf_container{}, #cf_container_cdn_config{}) -> ok | cferl_lib:cferl_error().
  102 +make_public(Connection, Container, CdnConfig)
  103 + when ?IS_CONNECTION(Connection), ?IS_CONTAINER(Container), is_record(CdnConfig, cf_container_cdn_config) ->
  104 + PutResult = cferl_connection:send_cdn_management_request(Connection, put, Container#cf_container.container_path, raw),
  105 + make_public_put_result(Connection, Container, CdnConfig, PutResult).
81 106
82   -make_public_put_result(CdnConfig, {ok, ResponseCode, _, _})
  107 +make_public_put_result(Connection, Container, CdnConfig, {ok, ResponseCode, _, _})
83 108 when ResponseCode =:= "201"; ResponseCode =:= "202" ->
84 109
85 110 CdnConfigHeaders = cferl_lib:cdn_config_to_headers(CdnConfig),
86 111 Headers = [{"X-CDN-Enabled", "True"} | CdnConfigHeaders],
87   - PostResult = Connection:send_cdn_management_request(post, ContainerPath, Headers, raw),
  112 + PostResult = cferl_connection:send_cdn_management_request(Connection, post, Container#cf_container.container_path, Headers, raw),
88 113 make_public_post_result(PostResult);
89   -make_public_put_result(_, Other) ->
  114 +make_public_put_result(_, _, _, Other) ->
90 115 cferl_lib:error_result(Other).
91 116
92 117 make_public_post_result({ok, ResponseCode, _, _})
@@ -97,11 +122,10 @@ make_public_post_result(Other) ->
97 122
98 123 %% @doc Make the current container private.
99 124 %% If it was previously public, it will remain accessible on the CDN until its TTL is reached.
100   -%% @spec make_private() -> ok | Error
101   -%% Error = cferl_error()
102   -make_private() ->
  125 +-spec make_private(#cf_connection{}, #cf_container{}) -> ok | cferl_lib:cferl_error().
  126 +make_private(Connection, Container) when ?IS_CONNECTION(Connection), ?IS_CONTAINER(Container) ->
103 127 Headers = [{"X-CDN-Enabled", "False"}],
104   - PostResult = Connection:send_cdn_management_request(post, ContainerPath, Headers, raw),
  128 + PostResult = cferl_connection:send_cdn_management_request(Connection, post, Container#cf_container.container_path, Headers, raw),
105 129 make_private_result(PostResult).
106 130
107 131 make_private_result({ok, ResponseCode, _, _})
@@ -111,16 +135,15 @@ make_private_result(Other) ->
111 135 cferl_lib:error_result(Other).
112 136
113 137 %% @doc Activate or deactivate log retention for current container.
114   -%% @spec set_log_retention(true | false) -> ok | Error
115   -%% Error = cferl_error()
116   -set_log_retention(true) ->
117   - do_set_log_retention("True");
118   -set_log_retention(false) ->
119   - do_set_log_retention("False").
  138 +-spec set_log_retention(#cf_connection{}, #cf_container{}, true | false) -> ok | cferl_lib:cferl_error().
  139 +set_log_retention(Connection, Container, true) when ?IS_CONNECTION(Connection), ?IS_CONTAINER(Container) ->
  140 + do_set_log_retention(Connection, Container, "True");
  141 +set_log_retention(Connection, Container, false) when ?IS_CONNECTION(Connection), ?IS_CONTAINER(Container) ->
  142 + do_set_log_retention(Connection, Container, "False").
120 143
121   -do_set_log_retention(State) ->
  144 +do_set_log_retention(Connection, Container, State) ->
122 145 Headers = [{"x-log-retention", State}],
123   - PostResult = Connection:send_cdn_management_request(post, ContainerPath, Headers, raw),
  146 + PostResult = cferl_connection:send_cdn_management_request(Connection, post, Container#cf_container.container_path, Headers, raw),
124 147 set_log_retention_result(PostResult).
125 148
126 149 set_log_retention_result({ok, ResponseCode, _, _})
@@ -130,34 +153,29 @@ set_log_retention_result(Other) ->
130 153 cferl_lib:error_result(Other).
131 154
132 155 %% @doc Refresh the current container reference.
133   -%% @spec refresh() -> {ok, Container} | Error
134   -%% Container = cferl_container()
135   -%% Error = cferl_error()
136   -refresh() ->
137   - Connection:get_container(name()).
  156 +-spec refresh(#cf_connection{}, #cf_container{}) -> {ok, #cf_container{}} | cferl_lib:cferl_error().
  157 +refresh(Connection, Container) when ?IS_CONNECTION(Connection), ?IS_CONTAINER(Container) ->
  158 + cferl_connection:get_container(Connection, name(Container)).
138 159
139 160 %% @doc Delete the current container (which must be empty).
140   -%% @spec delete() -> ok | Error
141   -%% Error = {error, not_empty} | cferl_error()
142   -delete() ->
143   - Connection:delete_container(name()).
  161 +-spec delete(#cf_connection{}, #cf_container{}) -> ok | {error, not_empty} | cferl_lib:cferl_error().
  162 +delete(Connection, Container) when ?IS_CONNECTION(Connection), ?IS_CONTAINER(Container) ->
  163 + cferl_connection:delete_container(Connection, name(Container)).
144 164
145 165 %% @doc Retrieve all the object names in the current container (within the limits imposed by Cloud Files server).
146   -%% @spec get_objects_names() -> {ok, [binary()]} | Error
147   -%% Error = cferl_error()
148   -get_objects_names() ->
149   - get_objects_names(#cf_object_query_args{}).
  166 +-spec get_objects_names(#cf_connection{}, #cf_container{}) -> {ok, [binary()]} | cferl_lib:cferl_error().
  167 +get_objects_names(Connection, Container) ->
  168 + get_objects_names(Connection, Container, #cf_object_query_args{}).
150 169
151 170 %% @doc Retrieve the object names in the current container, filtered by the provided query arguments.
152 171 %% If you supply the optional limit, marker, prefix or path arguments, the call will return the number of objects specified in limit,
153 172 %% starting at the object index specified in marker, selecting objects whose names start with prefix or search within the pseudo-filesystem
154 173 %% path.
155   -%% @spec get_objects_names(QueryArgs) -> {ok, [binary()]} | Error
156   -%% QueryArgs = cf_object_query_args()
157   -%% Error = cferl_error()
158   -get_objects_names(QueryArgs) when is_record(QueryArgs, cf_object_query_args) ->
  174 +-spec get_objects_names(#cf_connection{}, #cf_container{}, #cf_object_query_args{}) -> {ok, [binary()]} | cferl_lib:cferl_error().
  175 +get_objects_names(Connection, Container, QueryArgs)
  176 + when ?IS_CONNECTION(Connection), ?IS_CONTAINER(Container), is_record(QueryArgs, cf_object_query_args) ->
159 177 QueryString = cferl_lib:object_query_args_to_string(QueryArgs),
160   - Result = Connection:send_storage_request(get, ContainerPath ++ QueryString, raw),
  178 + Result = cferl_connection:send_storage_request(Connection, get, Container#cf_container.container_path ++ QueryString, raw),
161 179 get_objects_names_result(Result).
162 180
163 181 get_objects_names_result({ok, "204", _, _}) ->
@@ -168,21 +186,21 @@ get_objects_names_result(Other) ->
168 186 cferl_lib:error_result(Other).
169 187
170 188 %% @doc Retrieve details for all the objects in the current container (within the limits imposed by Cloud Files server).
171   -%% @spec get_objects_details() -> {ok, [cf_object_details()]} | Error
  189 +-spec get_objects_details(#cf_connection{}, #cf_container{}) -> {ok, [#cf_object_details{}]} | cferl_lib:cferl_error().
172 190 %% Error = cferl_error()
173   -get_objects_details() ->
174   - get_objects_details(#cf_object_query_args{}).
  191 +get_objects_details(Connection, Container) ->
  192 + get_objects_details(Connection, Container, #cf_object_query_args{}).
175 193
176 194 %% @doc Retrieve the object details in the current container, filtered by the provided query arguments.
177 195 %% If you supply the optional limit, marker, prefix or path arguments, the call will return the number of objects specified in limit,
178 196 %% starting at the object index specified in marker, selecting objects whose names start with prefix or search within the pseudo-filesystem
179 197 %% path.
180   -%% @spec get_objects_details(QueryArgs) -> {ok, [cf_object_details()]} | Error
181   -%% QueryArgs = cf_object_query_args()
182   -%% Error = cferl_error()
183   -get_objects_details(QueryArgs) when is_record(QueryArgs, cf_object_query_args) ->
  198 +-spec get_objects_details(#cf_connection{}, #cf_container{}, #cf_object_query_args{}) ->
  199 + {ok, [#cf_object_details{}]} | cferl_lib:cferl_error().
  200 +get_objects_details(Connection, Container, QueryArgs)
  201 + when ?IS_CONNECTION(Connection), ?IS_CONTAINER(Container), is_record(QueryArgs, cf_object_query_args) ->
184 202 QueryString = cferl_lib:object_query_args_to_string(QueryArgs),
185   - Result = Connection:send_storage_request(get, ContainerPath ++ QueryString, json),
  203 + Result = cferl_connection:send_storage_request(Connection, get, Container#cf_container.container_path ++ QueryString, json),
186 204 get_objects_details_result(Result).
187 205
188 206 get_objects_details_result({ok, "204", _, _}) ->
@@ -219,9 +237,9 @@ get_objects_details_result(Other) ->
219 237 cferl_lib:error_result(Other).
220 238
221 239 %% @doc Test the existence of an object in the current container.
222   -%% @spec object_exists(ObjectName::binary()) -> true | false
223   -object_exists(ObjectName) when is_binary(ObjectName) ->
224   - Result = Connection:send_storage_request(head, get_object_path(ObjectName), raw),
  240 +-spec object_exists(#cf_connection{}, #cf_container{}, ObjectName::binary()) -> true | false.
  241 +object_exists(Connection, Container, ObjectName) when ?IS_CONNECTION(Connection), is_binary(ObjectName) ->
  242 + Result = cferl_connection:send_storage_request(Connection, head, get_object_path(Container, ObjectName), raw),
225 243 object_exists_result(Result).
226 244
227 245 object_exists_result({ok, ResponseCode, _, _})
@@ -231,14 +249,13 @@ object_exists_result(_) ->
231 249 false.
232 250
233 251 %% @doc Get a reference to an existing storage object.
234   -%% @spec get_object(Name::binary) -> {ok, Object} | Error
235   -%% Object = cferl_object()
236   -%% Error = cferl_error()
237   -get_object(ObjectName) when is_binary(ObjectName) ->
238   - Result = Connection:send_storage_request(head, get_object_path(ObjectName), raw),
239   - get_object_result(ObjectName, Result).
  252 +-spec get_object(#cf_connection{}, #cf_container{}, Name::binary()) -> {ok, #cf_object{}} | cferl_lib:cferl_error().
  253 +get_object(Connection, Container, ObjectName)
  254 + when ?IS_CONNECTION(Connection), ?IS_CONTAINER(Container), is_binary(ObjectName) ->
  255 + Result = cferl_connection:send_storage_request(Connection, head, get_object_path(Container, ObjectName), raw),
  256 + get_object_result(ObjectName, Container, Result).
240 257
241   -get_object_result(ObjectName, {ok, ResponseCode, ResponseHeaders, _})
  258 +get_object_result(ObjectName, Container, {ok, ResponseCode, ResponseHeaders, _})
242 259 when ResponseCode =:= "200"; ResponseCode =:= "204" ->
243 260
244 261 ObjectDetails = #cf_object_details{
@@ -249,31 +266,31 @@ get_object_result(ObjectName, {ok, ResponseCode, ResponseHeaders, _})
249 266 etag = cferl_lib:get_binary_header("Etag", ResponseHeaders)
250 267 },
251 268
252   - {ok, cferl_object:new(Connection, THIS, ObjectDetails, get_object_path(ObjectName), ResponseHeaders)};
  269 + {ok, cferl_object:new(Container, ObjectDetails, get_object_path(Container, ObjectName), ResponseHeaders)};
253 270
254   -get_object_result(_, Other) ->
  271 +get_object_result(_, _, Other) ->
255 272 cferl_lib:error_result(Other).
256 273
257 274 %% @doc Create a reference to a new storage object.
258 275 %% Nothing is actually created until data gets written in the object.
259 276 %% If an object with the provided name already exists, a reference to this object is returned.
260   -%% @spec create_object(Name::binary) -> {ok, Object} | Error
261   -%% Object = cferl_object()
  277 +-spec create_object(#cf_connection{}, #cf_container{}, Name::binary()) -> {ok, #cf_object{}} | cferl_lib:cferl_error().
262 278 %% Error = cferl_error()
263   -create_object(ObjectName) when is_binary(ObjectName) ->
264   - case get_object(ObjectName) of
  279 +create_object(Connection, Container, ObjectName)
  280 + when ?IS_CONNECTION(Connection), ?IS_CONTAINER(Container), is_binary(ObjectName) ->
  281 + case get_object(Connection, Container, ObjectName) of
265 282 {ok, Object} ->
266 283 {ok, Object};
267 284 _ ->
268 285 ObjectDetails = #cf_object_details{name = ObjectName},
269   - {ok, cferl_object:new(Connection, THIS, ObjectDetails, get_object_path(ObjectName), [])}
  286 + {ok, cferl_object:new(Container, ObjectDetails, get_object_path(Container, ObjectName), [])}
270 287 end.
271 288
272 289 %% @doc Delete an existing storage object.
273   -%% @spec delete_object(Name::binary) -> ok | Error
274   -%% Error = cferl_error()
275   -delete_object(ObjectName) when is_binary(ObjectName) ->
276   - Result = Connection:send_storage_request(delete, get_object_path(ObjectName), raw),
  290 +-spec delete_object(#cf_connection{}, #cf_container{}, Name::binary()) -> ok | cferl_lib:cferl_error().
  291 +delete_object(Connection, Container, ObjectName)
  292 + when ?IS_CONNECTION(Connection), ?IS_CONTAINER(Container), is_binary(ObjectName) ->
  293 + Result = cferl_connection:send_storage_request(Connection, delete, get_object_path(Container, ObjectName), raw),
277 294 delete_object_result(Result).
278 295
279 296 delete_object_result({ok, "204", _, _}) ->
@@ -283,28 +300,36 @@ delete_object_result(Other) ->
283 300
284 301 %% @doc Ensure that all the directories exist in an object path.
285 302 %% Passing &lt;&lt;"photos/plants/fern.jpg">>, will ensure that the &lt;&lt;"photos">> and &lt;&lt;"photos/plants">> directories exist.
286   -%% @spec ensure_dir(ObjectPath::binary()) -> ok
287   -ensure_dir(ObjectPath) when is_binary(ObjectPath) ->
  303 +-spec ensure_dir(#cf_connection{}, #cf_container{}, ObjectPath::binary()) -> ok.
  304 +ensure_dir(Connection, Container, ObjectPath)
  305 + when ?IS_CONNECTION(Connection), ?IS_CONTAINER(Container), is_binary(ObjectPath) ->
288 306 CreateDirectoryFun =
289 307 fun(Directory) ->
290   - {ok, DirectoryObject} = create_object(Directory),
  308 + {ok, DirectoryObject} = create_object(Connection, Container, Directory),
291 309 % push the object on the server only if its content-type is not good
292   - case DirectoryObject:content_type() of
  310 + case cferl_object:content_type(DirectoryObject) of
293 311 ?DIRECTORY_OBJECT_CONTENT_TYPE ->
294 312 noop;
295 313 _ ->
296   - ok = DirectoryObject:write_data(<<>>,
297   - ?DIRECTORY_OBJECT_CONTENT_TYPE,
298   - [{<<"Content-Length">>, <<"0">>}])
  314 + ok = cferl_object:write_data(Connection, DirectoryObject, <<>>,
  315 + ?DIRECTORY_OBJECT_CONTENT_TYPE,
  316 + [{<<"Content-Length">>, <<"0">>}])
299 317 end
300 318 end,
301 319
302 320 lists:foreach(CreateDirectoryFun, cferl_lib:path_to_sub_dirs(ObjectPath)),
303 321 ok.
304 322
  323 +%% Friend functions
  324 +-spec new(#cf_container_details{}, string(), [{atom(), term()}]) -> #cf_container{}.
  325 +new(ContainerDetails, Path, CdnDetails) ->
  326 + #cf_container{container_details = ContainerDetails,
  327 + container_path = Path,
  328 + cdn_details = CdnDetails}.
  329 +
305 330 %% Private functions
306   -get_object_path(ObjectName) when is_binary(ObjectName) ->
307   - ContainerPath ++ "/" ++ cferl_lib:url_encode(ObjectName).
  331 +get_object_path(Container, ObjectName) when ?IS_CONTAINER(Container), is_binary(ObjectName) ->
  332 + Container#cf_container.container_path ++ "/" ++ cferl_lib:url_encode(ObjectName).
308 333
309 334 bin_to_int(Bin) when is_binary(Bin) ->
310 335 list_to_integer(binary_to_list(Bin)).
6 src/cferl_lib.erl
@@ -20,6 +20,12 @@
20 20
21 21 -define(TEST_HEADERS, [{"int", "123"}, {"bool", "true"}, {"str", "abc"}]).
22 22
  23 +
  24 +%%% @type cferl_error() = {error, not_found} | {error, unauthorized} | {error, {unexpected_response, Other}}.
  25 +-type(cferl_error() :: {error, term()}).
  26 +
  27 +-export_type([cferl_error/0]).
  28 +
23 29 -ifdef(TEST).
24 30 -include_lib("eunit/include/eunit.hrl").
25 31 -endif.
193 src/cferl_object.erl
@@ -5,57 +5,77 @@
5 5 %%% See LICENSE for license information.
6 6 %%% Copyright (c) 2010 David Dossot
7 7 %%%
8   -%%% @type cferl_error() = {error, not_found} | {error, unauthorized} | {error, {unexpected_response, Other}}.
9   -%%% @type cferl_object() = term(). Reference to the cferl_object parameterized module.
10 8
11   --module(cferl_object, [Connection, Container, ObjectDetails, ObjectPath, HttpHeaders]).
  9 +-module(cferl_object).
12 10 -author('David Dossot <david@dossot.net>').
13 11 -include("cferl.hrl").
14 12
15   --export([name/0, bytes/0, last_modified/0, content_type/0, etag/0,
16   - metadata/0, set_metadata/1, refresh/0,
17   - read_data/0, read_data/2, read_data_stream/1, read_data_stream/3,
18   - write_data/2, write_data/3, write_data_stream/3, write_data_stream/4,
19   - delete/0]).
  13 +%% Public API
  14 +-export([name/1,
  15 + bytes/1,
  16 + last_modified/1,
  17 + content_type/1,
  18 + etag/1,
  19 + metadata/1,
  20 + set_metadata/3,
  21 + refresh/2,
  22 + read_data/2,
  23 + read_data/4,
  24 + read_data_stream/3,
  25 + read_data_stream/5,
<