cloudfs 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +13 -0
- data/lib/cloudfs.rb +18 -0
- data/lib/cloudfs/account.rb +95 -0
- data/lib/cloudfs/client/connection.rb +154 -0
- data/lib/cloudfs/client/constants.rb +80 -0
- data/lib/cloudfs/client/error.rb +452 -0
- data/lib/cloudfs/client/utils.rb +93 -0
- data/lib/cloudfs/container.rb +51 -0
- data/lib/cloudfs/file.rb +209 -0
- data/lib/cloudfs/filesystem.rb +111 -0
- data/lib/cloudfs/filesystem_common.rb +228 -0
- data/lib/cloudfs/folder.rb +94 -0
- data/lib/cloudfs/item.rb +640 -0
- data/lib/cloudfs/media.rb +32 -0
- data/lib/cloudfs/rest_adapter.rb +1233 -0
- data/lib/cloudfs/session.rb +256 -0
- data/lib/cloudfs/share.rb +286 -0
- data/lib/cloudfs/user.rb +107 -0
- data/lib/cloudfs/version.rb +3 -0
- data/spec/account_spec.rb +93 -0
- data/spec/container_spec.rb +37 -0
- data/spec/file_spec.rb +134 -0
- data/spec/filesystem_spec.rb +16 -0
- data/spec/folder_spec.rb +106 -0
- data/spec/item_spec.rb +194 -0
- data/spec/session_spec.rb +102 -0
- data/spec/share_spec.rb +159 -0
- data/spec/user_spec.rb +70 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f0922a7cd038e8bf4db5460e34f07946828a1561
|
4
|
+
data.tar.gz: cf488972f0f1b095944c665799ffb57806435971
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a675b40f92dbb67f390a1a8ba1168f73677248732be245f8eb2d31a95b9d5e6362eabc2064a677b7fe6f8e5bceb6894df242049b1a1f8749ed936558946a6685
|
7
|
+
data.tar.gz: d435d4a6265c4c304dda9cea5b3974d543151eb8036a2908ca6a2a01592a59335b29b72905993508458c6dc28e984e1a0dc5ce8d1accce7e7964889f22840b8e
|
data/.yardopts
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
--markup markdown
|
2
|
+
--template-path yard
|
3
|
+
--title 'Bitcasa SDK for Ruby'
|
4
|
+
--no-private
|
5
|
+
--no-highlight
|
6
|
+
--tag optimize:"OPTIMIZE" #--hide-tag optimize
|
7
|
+
--tag review:"REVIEW" #--hide-tag review
|
8
|
+
#--hide-tag todo
|
9
|
+
#--hide-api private
|
10
|
+
#--hide-void-return
|
11
|
+
--hide-tag author
|
12
|
+
'lib/**/*.rb' - '*.md'
|
13
|
+
-
|
data/lib/cloudfs.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'cloudfs/version'
|
2
|
+
require_relative 'cloudfs/rest_adapter'
|
3
|
+
require_relative 'cloudfs/user'
|
4
|
+
require_relative 'cloudfs/account'
|
5
|
+
require_relative 'cloudfs/session'
|
6
|
+
require_relative 'cloudfs/item'
|
7
|
+
require_relative 'cloudfs/filesystem'
|
8
|
+
require_relative 'cloudfs/file'
|
9
|
+
require_relative 'cloudfs/container'
|
10
|
+
require_relative 'cloudfs/folder'
|
11
|
+
require_relative 'cloudfs/share'
|
12
|
+
require_relative 'cloudfs/media'
|
13
|
+
|
14
|
+
# This module enables application to consume Bitcasa CloudFS storage service
|
15
|
+
# by creating authenticated RESTful interfaces to filesystem objects
|
16
|
+
# in end-user's CloudFS account.
|
17
|
+
module CloudFS
|
18
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require_relative 'user'
|
2
|
+
module CloudFS
|
3
|
+
|
4
|
+
# Account class defines properties of the end-user's CloudFS paid account
|
5
|
+
#
|
6
|
+
# @author Mrinal Dhillon
|
7
|
+
class Account
|
8
|
+
|
9
|
+
# @!attribute [r] id
|
10
|
+
# @return [String] id of this user's account
|
11
|
+
def id
|
12
|
+
@properties[:id]
|
13
|
+
end
|
14
|
+
|
15
|
+
# @!attribute [r] storage_usage
|
16
|
+
# @return [Fixnum] current storage usage of account in bytes
|
17
|
+
def storage_usage
|
18
|
+
@properties[:storage][:usage]
|
19
|
+
end
|
20
|
+
|
21
|
+
# @!attribute [r] storage_limit
|
22
|
+
# @return [Fixnum] storage limit of account in bytes
|
23
|
+
def storage_limit
|
24
|
+
@properties[:storage][:limit]
|
25
|
+
end
|
26
|
+
|
27
|
+
# @!attribute [r] over_storage_limit
|
28
|
+
# @return [Boolean] whether user is currently over its storage quota
|
29
|
+
def over_storage_limit
|
30
|
+
@properties[:storage][:otl]
|
31
|
+
end
|
32
|
+
|
33
|
+
# @!attribute [r] state_id
|
34
|
+
# @return [String] id of current account state
|
35
|
+
def state_id
|
36
|
+
@properties[:account_state][:id]
|
37
|
+
end
|
38
|
+
|
39
|
+
# @!attribute [r] state_display_name
|
40
|
+
# @return [String] Human readable name of account's CloudFS state
|
41
|
+
def state_display_name
|
42
|
+
@properties[:account_state][:display_name]
|
43
|
+
end
|
44
|
+
|
45
|
+
# @!attribute [r] plan_display_name
|
46
|
+
# @return [String] Human readable name of account's CloudFS plan
|
47
|
+
def plan_display_name
|
48
|
+
@properties[:account_plan][:display_name]
|
49
|
+
end
|
50
|
+
|
51
|
+
# @!attribute [r] plan_id
|
52
|
+
# @return [String] id of CloudFS plan
|
53
|
+
def plan_id
|
54
|
+
@properties[:account_plan][:id]
|
55
|
+
end
|
56
|
+
|
57
|
+
# @!attribute [r] session_locale
|
58
|
+
# @return [String] locale of current session
|
59
|
+
def session_locale
|
60
|
+
@properties[:session][:locale]
|
61
|
+
end
|
62
|
+
|
63
|
+
# @!attribute [r] account_locale
|
64
|
+
# @return [String] locale of the entire account
|
65
|
+
def account_locale
|
66
|
+
@properties[:locale]
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param rest_adapter [RestAdapter] cloudfs RESTful api object
|
70
|
+
# @param [Hash] properties metadata of account
|
71
|
+
def initialize(rest_adapter, ** properties)
|
72
|
+
fail RestAdapter::Errors::ArgumentError,
|
73
|
+
"invalid RestAdapter type #{rest_adapter.class}, expected CloudFS::RestAdapter" unless rest_adapter.is_a?(CloudFS::RestAdapter)
|
74
|
+
|
75
|
+
@rest_adapter = rest_adapter
|
76
|
+
set_account_info(** properties)
|
77
|
+
end
|
78
|
+
|
79
|
+
# @see #initialize
|
80
|
+
# @review required parameters
|
81
|
+
def set_account_info(** properties)
|
82
|
+
@properties = properties
|
83
|
+
end
|
84
|
+
|
85
|
+
# Refresh this user's account metadata from server
|
86
|
+
# @return [Account] returns self
|
87
|
+
def refresh
|
88
|
+
response = @rest_adapter.get_profile
|
89
|
+
set_account_info(** response)
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
private :set_account_info
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'httpclient'
|
2
|
+
require_relative 'utils'
|
3
|
+
require_relative 'error'
|
4
|
+
require_relative 'constants'
|
5
|
+
|
6
|
+
module CloudFS
|
7
|
+
class RestAdapter
|
8
|
+
# Provides RESTful interface
|
9
|
+
#
|
10
|
+
# @author Mrinal Dhillon
|
11
|
+
# Maintains a persistent instance of class HTTPClient,
|
12
|
+
# since HTTPClient instance is MT-safe and can be called from
|
13
|
+
# several threads without synchronization after setting up an instance,
|
14
|
+
# same behaviour is expected from Connection class.
|
15
|
+
#
|
16
|
+
# @see http://www.rubydoc.info/gems/httpclient
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# conn = Connection.new
|
20
|
+
# response = conn.request('GET', "https://www.example.com",
|
21
|
+
# :query => { :a => "b", :c => "d" })
|
22
|
+
# response = conn.request('POST', "https://www.example.com", :body => "a=b&c=d")
|
23
|
+
# response = conn.request('POST', "https://www.example.com",
|
24
|
+
# :body => { :a => "b", :c => "d"} )
|
25
|
+
class Connection
|
26
|
+
# Creates Connection instance
|
27
|
+
#
|
28
|
+
# @param params [Hash] connection configurations
|
29
|
+
# @option params [Fixnum] :connect_timeout (60) for server handshake,
|
30
|
+
# defualts to 60 as per httpclient documentation
|
31
|
+
# @option params [Fixnum] :send_timeout (120) for send request,
|
32
|
+
# defaults to 120 sec as per httpclient documentation, set 0 for no timeout
|
33
|
+
# @option params [Fixnum] :receive_timeout (60) timeout for read per block,
|
34
|
+
# defaults to 60 sec as per httpclient documentation, set 0 for no timeout
|
35
|
+
# @option params [Fixnum] :max_retry (0) for http 500 level errors
|
36
|
+
# @option params [String] :agent_name (HTTPClient)
|
37
|
+
# @option params [#<<] :debug_dev (nil) provide http wire information
|
38
|
+
# from httpclient
|
39
|
+
def initialize(** params)
|
40
|
+
@persistent_conn = HTTPClient.new
|
41
|
+
@persistent_conn.cookie_manager = nil
|
42
|
+
|
43
|
+
connect_timeout, send_timeout, receive_timeout,
|
44
|
+
max_retries, debug_dev, agent_name =
|
45
|
+
params.values_at(:connect_timeout, :send_timeout, :receive_timeout,
|
46
|
+
:max_retries, :debug_dev, :agent_name)
|
47
|
+
@persistent_conn.connect_timeout = connect_timeout if connect_timeout
|
48
|
+
@persistent_conn.send_timeout = send_timeout if send_timeout
|
49
|
+
@persistent_conn.receive_timeout = receive_timeout if receive_timeout
|
50
|
+
@persistent_conn.debug_dev = debug_dev if debug_dev.respond_to?(:<<)
|
51
|
+
@persistent_conn.agent_name = agent_name
|
52
|
+
@max_retries = max_retries ? max_retries : 0
|
53
|
+
end
|
54
|
+
|
55
|
+
# Disconnects all keep alive connections and intenal sessions
|
56
|
+
def unlink
|
57
|
+
@persistent_conn.reset_all
|
58
|
+
end
|
59
|
+
|
60
|
+
# Sends request to specified url,
|
61
|
+
# calls HTTPClient#request, retries http 500 level errors with
|
62
|
+
# exponential delay upto max retries
|
63
|
+
#
|
64
|
+
# @param method [Symbol] (:get, :put, :post, :delete) http verb
|
65
|
+
# @param uri [String, URI] represents complete url to web resource
|
66
|
+
# @param params [Hash] http request parameters i.e. :headers, :query, :body
|
67
|
+
# @option params [Hash] :header http request headers
|
68
|
+
# @option params [Hash] :query part of url -
|
69
|
+
# "https://host/path?key=value&key1=value1"
|
70
|
+
# @option params [Array<Hash>, Hash, String] :body {} to post multipart forms,
|
71
|
+
# key:value forms, string
|
72
|
+
#
|
73
|
+
# @return [Hash] response hash containing content, content_type and http code
|
74
|
+
# { :content => String, :content_type => String, :code => Fixnum }
|
75
|
+
# @raise [Errors::ClientError, Errors::ServerError]
|
76
|
+
# ClientError wraps httpclient exceptions
|
77
|
+
# i.e. timeout, connection failed etc.
|
78
|
+
# ServerError contains error message and code from server
|
79
|
+
# @optimize async request support
|
80
|
+
#
|
81
|
+
# @review Behaviour in case of error with follow_redirect set to true
|
82
|
+
# with callback block for get: observed is that if server return
|
83
|
+
# message as response body in case of error, message is discarded
|
84
|
+
# and unable to fetch it. Opened issue#234 on nahi/httpclient github.
|
85
|
+
# Currently fetching HTTP::Message#reason if HTTP::Message#content
|
86
|
+
# is not available in such case
|
87
|
+
# @review exceptions raised by HTTPClient should not be handled
|
88
|
+
def request(method, uri, ** params, &block)
|
89
|
+
method = method.to_s.downcase.to_sym
|
90
|
+
req_params = params.reject { |_, v| Utils.is_blank?(v) }
|
91
|
+
|
92
|
+
if method == :get && !params[:header].has_key?(Constants::HEADER_REDIRECT)
|
93
|
+
req_params = req_params.merge({follow_redirect: true})
|
94
|
+
end
|
95
|
+
|
96
|
+
resp = request_with_retry(method, uri, req_params, &block)
|
97
|
+
|
98
|
+
status = resp.status.to_i
|
99
|
+
response = {code: status}
|
100
|
+
response[:content] = resp.content
|
101
|
+
response[:content_type] = resp.header['Content-Type'].first
|
102
|
+
if status < 200 || status >=400 || (resp.redirect? && status != 302)
|
103
|
+
message = Utils.is_blank?(resp.content) ? resp.reason : resp.content
|
104
|
+
request = set_error_request_context(method, uri, req_params)
|
105
|
+
fail Errors::ServerError.new(message, status, response, request)
|
106
|
+
end
|
107
|
+
response
|
108
|
+
|
109
|
+
rescue HTTPClient::TimeoutError
|
110
|
+
request = set_error_request_context(method, uri, req_params)
|
111
|
+
raise Errors::TimeoutError.new($!, request)
|
112
|
+
rescue HTTPClient::BadResponseError
|
113
|
+
request = set_error_request_context(method, uri, req_params)
|
114
|
+
raise Errors::ClientError.new($!, request)
|
115
|
+
rescue Errno::ECONNREFUSED, EOFError, SocketError
|
116
|
+
request = set_error_request_context(method, uri, req_params)
|
117
|
+
raise Errors::ConnectionFailed.new($!, request)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Retries HTTP 500 error upto max retries
|
121
|
+
# @see request for request and response parameters
|
122
|
+
def request_with_retry(method, uri, req_params, &block)
|
123
|
+
retry_count = 0
|
124
|
+
loop do
|
125
|
+
response = @persistent_conn.request(method, uri, req_params, &block)
|
126
|
+
retry_count += 1
|
127
|
+
break response unless (response.status.to_i >= 500) && do_retry?(retry_count)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Check if retry count is less that max retries and exponentially sleep
|
132
|
+
# @param retry_count [Fixnum] current count of retry
|
133
|
+
# @return [Boolean]
|
134
|
+
def do_retry?(retry_count)
|
135
|
+
# max retries + 1 to accommodate try
|
136
|
+
retry_count < @max_retries + 1 ? sleep(2**retry_count*0.3) : false
|
137
|
+
end
|
138
|
+
|
139
|
+
# Set request context
|
140
|
+
# @see #request
|
141
|
+
def set_error_request_context(method, uri, request_params)
|
142
|
+
request = {uri: uri.to_s}
|
143
|
+
request[:method] = method.to_s
|
144
|
+
# @optimize copying params as string makes exception only informative,
|
145
|
+
# should instead return deep copy of request params so that
|
146
|
+
# applications can evaluate error.
|
147
|
+
request[:params] = request_params.to_s
|
148
|
+
request
|
149
|
+
end
|
150
|
+
|
151
|
+
private :set_error_request_context, :request_with_retry, :do_retry?
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module CloudFS
|
2
|
+
class RestAdapter
|
3
|
+
# @private
|
4
|
+
# Declares cloudfs constants
|
5
|
+
module Constants
|
6
|
+
HTTP_AGENT_NAME = 'BCSClient'
|
7
|
+
REQUEST_KEY_METHOD = 'method'
|
8
|
+
REQUEST_KEY_URI = 'uri'
|
9
|
+
HTTP_METHOD_KEY = 'method'
|
10
|
+
HTTP_METHOD_POST = 'POST'
|
11
|
+
HTTP_METHOD_GET = 'GET'
|
12
|
+
|
13
|
+
URI_PREFIX_HTTPS = 'https://'
|
14
|
+
# DATE_FORMAT define date format
|
15
|
+
DATE_FORMAT = '%a, %e %b %Y %H:%M:%S %Z'
|
16
|
+
# HEADER_DATE constant string
|
17
|
+
HEADER_DATE = 'Date'
|
18
|
+
# HEADER_CONTENT_TYP content-type string
|
19
|
+
HEADER_CONTENT_TYPE = 'Content-Type'
|
20
|
+
# HEADER_AUTHORIZATION authorization
|
21
|
+
HEADER_AUTH_PREFIX_BCS = 'BCS'
|
22
|
+
# HEADER_AUTHORIZATION authorization
|
23
|
+
HEADER_AUTHORIZATION = 'Authorization'
|
24
|
+
# HEADER_REDIRECT follow_redirect
|
25
|
+
HEADER_REDIRECT = 'follow_redirect'
|
26
|
+
# CONTENT_TYPE_APP_URLENCODED url for application
|
27
|
+
CONTENT_TYPE_APP_URLENCODED = 'application/x-www-form-urlencoded;charset=utf-8'
|
28
|
+
# CONTENT_TYPE_MULTI content type for multipart
|
29
|
+
CONTENT_TYPE_MULTI = 'multipart/form-data'
|
30
|
+
# HEADER_CONTENT_TYPE_APP_URLENCODED
|
31
|
+
HEADER_CONTENT_TYPE_APP_URLENCODED = {"#{HEADER_CONTENT_TYPE}" =>
|
32
|
+
"#{CONTENT_TYPE_APP_URLENCODED}"}
|
33
|
+
|
34
|
+
PARAM_EMAIL = 'email'
|
35
|
+
PARAM_FIRST_NAME = 'first_name'
|
36
|
+
PARAM_LAST_NAME = 'last_name'
|
37
|
+
# PARAM_GRANT_TYPE grant_type
|
38
|
+
PARAM_GRANT_TYPE = 'grant_type'
|
39
|
+
# PARAM_USER for username
|
40
|
+
PARAM_USER = 'username'
|
41
|
+
# PARAM_PASSWORD for password
|
42
|
+
PARAM_PASSWORD = 'password'
|
43
|
+
KEY_ENDPOINT = 'endpoint'
|
44
|
+
# ENDPOINT_OAUTH for oauth2 token
|
45
|
+
ENDPOINT_OAUTH = '/v2/oauth2/token'
|
46
|
+
# ENDPOINT_PING for ping
|
47
|
+
ENDPOINT_PING = '/v2/ping'
|
48
|
+
# ENDPOINT_CUSTOMERS defines admin cloudfs customers
|
49
|
+
ENDPOINT_CUSTOMERS = '/v2/admin/cloudfs/customers/'
|
50
|
+
# ENDPOINT_USER_PROFILE for user profile
|
51
|
+
ENDPOINT_USER_PROFILE = '/v2/user/profile/'
|
52
|
+
# ENDPOINT_FOLDERS for folders
|
53
|
+
ENDPOINT_FOLDERS = '/v2/folders/'
|
54
|
+
# ENDPOINT_FILES for files
|
55
|
+
ENDPOINT_FILES = '/v2/files/'
|
56
|
+
# ENDPOINT_FILES for files
|
57
|
+
ENDPOINT_ITEM = '/v2/files/' # TODO should change this path to /v2/filesystem/root/<path>/meta after REST fix
|
58
|
+
# ENDPOINT_SHARES for share folder
|
59
|
+
ENDPOINT_SHARES = '/v2/shares/'
|
60
|
+
# ENDPOINT_HISTORY for history
|
61
|
+
ENDPOINT_HISTORY = '/v2/history'
|
62
|
+
# ENDPOINT_TRASH for trash
|
63
|
+
ENDPOINT_TRASH = '/v2/trash/'
|
64
|
+
# QUERY_OPS_CREATE creates query ops
|
65
|
+
QUERY_OPS_CREATE = 'create'
|
66
|
+
# QUERY_OPS_COPY for copying query ops
|
67
|
+
QUERY_OPS_COPY = 'copy'
|
68
|
+
# QUERY_OPS_MOVE for move
|
69
|
+
QUERY_OPS_MOVE = 'move'
|
70
|
+
# QUERY_OPS_PROMOTE for promote
|
71
|
+
QUERY_OPS_PROMOTE = 'promote'
|
72
|
+
# EXISTS for fail, overwrite, rename & reuse actions
|
73
|
+
EXISTS = {FAIL: 'fail', OVERWRITE: 'overwrite', RENAME: 'rename'}
|
74
|
+
# VERSION_CONFLICT for fail or ignore.
|
75
|
+
VERSION_EXISTS = {FAIL: 'fail', IGNORE: 'ignore'}
|
76
|
+
# RESTORE fail, rescue & recreate action
|
77
|
+
RESTORE_METHOD = {FAIL: 'fail', RESCUE: 'rescue', RECREATE: 'recreate'}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,452 @@
|
|
1
|
+
require_relative 'utils'
|
2
|
+
|
3
|
+
module CloudFS
|
4
|
+
class RestAdapter
|
5
|
+
# Defines exceptional behavior.
|
6
|
+
# {Error} is base class of all exceptions raised by CloudFS SDK.
|
7
|
+
# {Errors::ServiceError} is base class of all errors returned by CloudFS service.
|
8
|
+
# Other exceptions are {Errors::ArgumentError}, {Errors::ClientError},
|
9
|
+
# {Errors::InvalidItemError}, {Errors::InvalidShareError},
|
10
|
+
# {Errors::OperationNotAllowedError}, {Errors::SessionNotLinked}
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# begin
|
14
|
+
# rest_adapter.ping
|
15
|
+
# rescue RestAdapter::Errors::SessionNotLinked
|
16
|
+
# rest_adapter.authenticate(username, password)
|
17
|
+
# retry
|
18
|
+
# rescue RestAdapter::Errors::ServiceError => error
|
19
|
+
# puts error.message
|
20
|
+
# puts error.request
|
21
|
+
# puts error.response
|
22
|
+
# puts error.code
|
23
|
+
# rescue RestAdapter::Errors::Error => error
|
24
|
+
# puts error.message
|
25
|
+
# puts error.backtrace
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# @author Mrinal Dhillon
|
29
|
+
module Errors
|
30
|
+
|
31
|
+
# Maps exception classes to error codes returned by CloudFS Service
|
32
|
+
BITCASA_ERRORS = {
|
33
|
+
9999 => 'GeneralPanicError',
|
34
|
+
9000 => 'APIError',
|
35
|
+
9006 => 'APICallLimitReached',
|
36
|
+
|
37
|
+
# FileSystem errors
|
38
|
+
8001 => 'InvalidVersion',
|
39
|
+
8002 => 'VersionMismatchIgnored',
|
40
|
+
8004 => 'OrigionalPathNoLongerExists',
|
41
|
+
|
42
|
+
# Shares errors
|
43
|
+
6001 => 'SharePathRequired',
|
44
|
+
6002 => 'SharePathDoesNotExist',
|
45
|
+
6003 => 'WouldExceedQuota',
|
46
|
+
6004 => 'ShareDoesNotExist',
|
47
|
+
|
48
|
+
# Folder errors
|
49
|
+
2002 => 'FolderDoesNotExist',
|
50
|
+
2003 => 'FolderNotFound',
|
51
|
+
2004 => 'UploadToReadOnlyDestinationFailed',
|
52
|
+
2005 => 'MoveToReadOnlyDestinationFailed',
|
53
|
+
2006 => 'CopyToReadOnlyDestinationFailed',
|
54
|
+
2007 => 'RenameOnReadOnlyLocationFailed',
|
55
|
+
2008 => 'DeleteOnReadOnlyLocationFailed',
|
56
|
+
2009 => 'CreateFolderOnReadOnlyLocationFailed',
|
57
|
+
2010 => 'FailedToReadFilesystem',
|
58
|
+
2011 => 'FailedToReadFilesystem',
|
59
|
+
2012 => 'FailedToReadFilesystem',
|
60
|
+
2013 => 'FailedToReadFilesystem',
|
61
|
+
2014 => 'NameConflictCreatingFolder',
|
62
|
+
2015 => 'NameConflictOnUpload',
|
63
|
+
2016 => 'NameConflictOnRename',
|
64
|
+
2017 => 'NameConflictOnMove',
|
65
|
+
2018 => 'NameConflictOnCopy',
|
66
|
+
2019 => 'FailedToSaveChanges',
|
67
|
+
2020 => 'FailedToSaveChanges',
|
68
|
+
2021 => 'FailedToSaveChanges',
|
69
|
+
2022 => 'FailedToBroadcastUpdate',
|
70
|
+
2023 => 'FailedToBroadcastUpdate',
|
71
|
+
2024 => 'FailedToSaveChanges',
|
72
|
+
2025 => 'FailedToSaveChanges',
|
73
|
+
2026 => 'CannotDeleteTheInfiniteDrive',
|
74
|
+
2028 => 'MissingToParameter"',
|
75
|
+
2033 => 'ExistsParameterInvalid',
|
76
|
+
2034 => 'MissingPathParameter',
|
77
|
+
2036 => 'SpecifiedLocationIsReadOnly',
|
78
|
+
2037 => 'SpecifiedSourceIsReadOnly',
|
79
|
+
2038 => 'SpecifiedDestinationIsReadOnly',
|
80
|
+
2039 => 'FolderPathDoesNotExist',
|
81
|
+
2040 => 'PermissionDenied',
|
82
|
+
2041 => 'RenamePermissionDenied',
|
83
|
+
2042 => 'NameConflictInOperation',
|
84
|
+
2043 => 'InvalidOperation',
|
85
|
+
2044 => 'VersionMissingOrIncorrect',
|
86
|
+
2045 => 'InvalidDepth',
|
87
|
+
2046 => 'VersionDoesNotExist',
|
88
|
+
2047 => 'FolderNameRequired',
|
89
|
+
2048 => 'InvalidName',
|
90
|
+
2049 => 'TreeRequired',
|
91
|
+
2050 => 'InvalidVerbose',
|
92
|
+
2052 => 'DirectoryNotEmpty',
|
93
|
+
|
94
|
+
# File errors
|
95
|
+
3001 => 'NotFound',
|
96
|
+
3007 => 'InvalidOperation',
|
97
|
+
3008 => 'InvalidName',
|
98
|
+
3009 => 'InvalidExists',
|
99
|
+
3010 => 'ExtensionTooLong',
|
100
|
+
3011 => 'InvalidDateCreated',
|
101
|
+
3012 => 'InvalidDateMetaLastModified',
|
102
|
+
3013 => 'InvalidDateContentLastModified',
|
103
|
+
3014 => 'MIMETooLong',
|
104
|
+
3015 => 'SizeMustBePositive',
|
105
|
+
3018 => 'NameRequired',
|
106
|
+
3019 => 'SizeRequired',
|
107
|
+
3020 => 'ToPathRequired',
|
108
|
+
3021 => 'VersionMissingOrIncorrect',
|
109
|
+
|
110
|
+
# Endpoint Entry Errors
|
111
|
+
10000 => 'InvalidPath',
|
112
|
+
10001 => 'AlreadyExists',
|
113
|
+
10002 => 'NotAllowed'
|
114
|
+
}
|
115
|
+
|
116
|
+
# All errors can be rescued by Errors::Error
|
117
|
+
# Top most error class, all cloudfs exceptions can be rescued by this class
|
118
|
+
class Error < StandardError;
|
119
|
+
end
|
120
|
+
|
121
|
+
# Item does not exists anymore, this is possible when item has been deleted
|
122
|
+
class InvalidItemError < Error;
|
123
|
+
end
|
124
|
+
|
125
|
+
# Share does not exists anymore, this is possible when share has been deleted
|
126
|
+
class InvalidShareError < Error;
|
127
|
+
end
|
128
|
+
|
129
|
+
# Operation not allowed error
|
130
|
+
class OperationNotAllowedError < Error;
|
131
|
+
end
|
132
|
+
|
133
|
+
# Invalid Argument error
|
134
|
+
class ArgumentError < Error;
|
135
|
+
end
|
136
|
+
|
137
|
+
# Session not linked error points out that either session
|
138
|
+
# is not authenticated or has been unlinked
|
139
|
+
class SessionNotLinked < Error
|
140
|
+
def initialize
|
141
|
+
super('session is not linked, please authenticate')
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# All HTTP errors can be rescued by Errors::HttpError
|
146
|
+
class HttpError < Error;
|
147
|
+
end
|
148
|
+
|
149
|
+
# Base class of Client side errors - {ConnectionFailed}, {TimeoutError}
|
150
|
+
class ClientError < HttpError
|
151
|
+
# @return [Fixnum] http status
|
152
|
+
attr_reader :code
|
153
|
+
# @return [ { :content => "HTTPClient Error",
|
154
|
+
# :content_type => "application/text", :code => -1 } ] response
|
155
|
+
# Is not informative, see backtrace for more info
|
156
|
+
attr_reader :response
|
157
|
+
# @return [Hash]
|
158
|
+
# { :uri => String, :method => String, :params => String }
|
159
|
+
attr_reader :request
|
160
|
+
|
161
|
+
# @param error [Exception, String]
|
162
|
+
# @param request [Hash] request context
|
163
|
+
def initialize(error, request={})
|
164
|
+
if error.respond_to?(:backtrace)
|
165
|
+
super(error.message)
|
166
|
+
@original = error
|
167
|
+
@code = -1
|
168
|
+
@request = request
|
169
|
+
# nothing informative to provide here
|
170
|
+
@response = {:content => 'HTTPClient Error',
|
171
|
+
:content_type => 'application/text', :code => -1}
|
172
|
+
else
|
173
|
+
super(error.to_s)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# @return [String] backtrace of original exception
|
178
|
+
def backtrace
|
179
|
+
@original.backtrace if @original && @original.respond_to?(:backtrace)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Client side error when host is not reachable
|
184
|
+
class ConnectionFailed < ClientError;
|
185
|
+
end
|
186
|
+
|
187
|
+
# Client side error when excution is expired due to send and receive time out
|
188
|
+
class TimeoutError < ClientError;
|
189
|
+
end
|
190
|
+
|
191
|
+
# Exception for errors returned by remote service
|
192
|
+
class ServerError < HttpError
|
193
|
+
# @return [Fixnum] http status
|
194
|
+
attr_reader :code
|
195
|
+
# @return [Hash] response
|
196
|
+
# { :content => String, :content_type => String, :code => Fixnum }
|
197
|
+
attr_reader :response
|
198
|
+
# @return [Hash] request context
|
199
|
+
# { :uri => String, :method => String, :params => String }
|
200
|
+
attr_reader :request
|
201
|
+
|
202
|
+
# @param message [String] error message
|
203
|
+
# @param code [Fixnum] error code
|
204
|
+
# @param response [Hash] service response body
|
205
|
+
# @param request [Hash] service request body
|
206
|
+
def initialize(message, code, response={}, request={})
|
207
|
+
super(message)
|
208
|
+
@code = code
|
209
|
+
@response = response
|
210
|
+
@request = request
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Base class of all errors returned by cloudfs service
|
215
|
+
class ServiceError < Error
|
216
|
+
# @param message [String] error message
|
217
|
+
# @param original [Exception] original exception
|
218
|
+
def initialize(message, original=nil)
|
219
|
+
super(message)
|
220
|
+
@original = original
|
221
|
+
end
|
222
|
+
|
223
|
+
# @attribute [r] request
|
224
|
+
# @return [Hash] request context
|
225
|
+
def request
|
226
|
+
if @original.respond_to?(:request)
|
227
|
+
@original.request
|
228
|
+
else
|
229
|
+
{}
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# @attribute [r] response
|
234
|
+
# @return [Hash] response context
|
235
|
+
def response
|
236
|
+
if @original.respond_to?(:response)
|
237
|
+
@original.response
|
238
|
+
else
|
239
|
+
{}
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# @attribute [r] code
|
244
|
+
# @return [Fixnum] http status
|
245
|
+
def code
|
246
|
+
if @original.respond_to?(:code)
|
247
|
+
@original.code
|
248
|
+
else
|
249
|
+
-1
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# @return [String] backtrace of original exception
|
254
|
+
def backtrace
|
255
|
+
@original.backtrace if @original && @original.respond_to?(:backtrace)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
class GeneralPanicError < ServiceError;
|
260
|
+
end
|
261
|
+
class APIError < ServiceError;
|
262
|
+
end
|
263
|
+
class APICallLimitReached < ServiceError;
|
264
|
+
end
|
265
|
+
|
266
|
+
# Base class for filesystem errors returned by cloudfs service.
|
267
|
+
class FileSystemError < ServiceError;
|
268
|
+
end
|
269
|
+
|
270
|
+
# Base class for share errors returned by cloudfs service.
|
271
|
+
class ShareError < ServiceError;
|
272
|
+
end
|
273
|
+
|
274
|
+
# Base class for folder errors returned by cloudfs service.
|
275
|
+
class FolderError < ServiceError;
|
276
|
+
end
|
277
|
+
|
278
|
+
# Base class for file errors returned by cloudfs service.
|
279
|
+
class FileError < ServiceError;
|
280
|
+
end
|
281
|
+
|
282
|
+
# Base class for endpoint errors returned by cloudfs service.
|
283
|
+
class EndpointError < ServiceError;
|
284
|
+
end
|
285
|
+
|
286
|
+
# FileSystem Errors
|
287
|
+
|
288
|
+
class InvalidVersion < FileSystemError;
|
289
|
+
end
|
290
|
+
class VersionMismatchIgnored < FileSystemError;
|
291
|
+
end
|
292
|
+
class OrigionalPathNoLongerExists < FileSystemError;
|
293
|
+
end
|
294
|
+
|
295
|
+
# Share Errors
|
296
|
+
|
297
|
+
class SharePathRequired < ShareError;
|
298
|
+
end
|
299
|
+
class SharePathDoesNotExist < ShareError;
|
300
|
+
end
|
301
|
+
class WouldExceedQuota < ShareError;
|
302
|
+
end
|
303
|
+
class ShareDoesNotExist < ShareError;
|
304
|
+
end
|
305
|
+
|
306
|
+
# Folder Errors
|
307
|
+
|
308
|
+
class FolderDoesNotExist < FolderError;
|
309
|
+
end
|
310
|
+
class FolderNotFound < FolderError;
|
311
|
+
end
|
312
|
+
class UploadToReadOnlyDestinationFailed < FolderError;
|
313
|
+
end
|
314
|
+
class MoveToReadOnlyDestinationFailed < FolderError;
|
315
|
+
end
|
316
|
+
class CopyToReadOnlyDestinationFailed < FolderError;
|
317
|
+
end
|
318
|
+
class RenameOnReadOnlyLocationFailed < FolderError;
|
319
|
+
end
|
320
|
+
class DeleteOnReadOnlyLocationFailed < FolderError;
|
321
|
+
end
|
322
|
+
class CreateFolderOnReadOnlyLocationFailed < FolderError;
|
323
|
+
end
|
324
|
+
class FailedToReadFilesystem < FolderError;
|
325
|
+
end
|
326
|
+
class NameConflictCreatingFolder < FolderError;
|
327
|
+
end
|
328
|
+
class NameConflictOnUpload < FolderError;
|
329
|
+
end
|
330
|
+
class NameConflictOnRename < FolderError;
|
331
|
+
end
|
332
|
+
class NameConflictOnMove < FolderError;
|
333
|
+
end
|
334
|
+
class NameConflictOnCopy < FolderError;
|
335
|
+
end
|
336
|
+
class FailedToSaveChanges < FolderError;
|
337
|
+
end
|
338
|
+
class FailedToBroadcastUpdate < FolderError;
|
339
|
+
end
|
340
|
+
class CannotDeleteTheInfiniteDrive < FolderError;
|
341
|
+
end
|
342
|
+
class FolderMissingToParameter < FolderError;
|
343
|
+
end
|
344
|
+
class ExistsParameterInvalid < FolderError;
|
345
|
+
end
|
346
|
+
class MissingPathParameter < FolderError;
|
347
|
+
end
|
348
|
+
class SpecifiedLocationIsReadOnly < FolderError;
|
349
|
+
end
|
350
|
+
class SpecifiedSourceIsReadOnly < FolderError;
|
351
|
+
end
|
352
|
+
class SpecifiedDestinationIsReadOnly < FolderError;
|
353
|
+
end
|
354
|
+
class FolderPathDoesNotExist < FolderError;
|
355
|
+
end
|
356
|
+
class PermissionDenied < FolderError;
|
357
|
+
end
|
358
|
+
class RenamePermissionDenied < FolderError;
|
359
|
+
end
|
360
|
+
class NameConflictInOperation < FolderError;
|
361
|
+
end
|
362
|
+
class InvalidOperation < FolderError;
|
363
|
+
end
|
364
|
+
class VersionMissingOrIncorrect < FolderError;
|
365
|
+
end
|
366
|
+
class InvalidDepth < FolderError;
|
367
|
+
end
|
368
|
+
class VersionMissingOrIncorrect < FolderError;
|
369
|
+
end
|
370
|
+
class VersionDoesNotExist < FolderError;
|
371
|
+
end
|
372
|
+
class FolderNameRequired < FolderError;
|
373
|
+
end
|
374
|
+
class InvalidName < FolderError;
|
375
|
+
end
|
376
|
+
class TreeRequired < FolderError;
|
377
|
+
end
|
378
|
+
class InvalidVerbose < FolderError;
|
379
|
+
end
|
380
|
+
class DirectoryNotEmpty < FolderError;
|
381
|
+
end
|
382
|
+
|
383
|
+
# FileErrors
|
384
|
+
|
385
|
+
class SizeRequired < FileError;
|
386
|
+
end
|
387
|
+
class NotFound < FileError;
|
388
|
+
end
|
389
|
+
class FileInvalidOperation < FileError;
|
390
|
+
end
|
391
|
+
class FileInvalidName < FileError;
|
392
|
+
end
|
393
|
+
class InvalidExists < FileError;
|
394
|
+
end
|
395
|
+
class ExtensionTooLong < FileError;
|
396
|
+
end
|
397
|
+
class InvalidDateCreated < FileError;
|
398
|
+
end
|
399
|
+
class InvalidDateMetaLastModified < FileError;
|
400
|
+
end
|
401
|
+
class InvalidDateContentLastModified < FileError;
|
402
|
+
end
|
403
|
+
class MIMETooLong < FileError;
|
404
|
+
end
|
405
|
+
class SizeMustBePositive < FileError;
|
406
|
+
end
|
407
|
+
class NameRequired < FileError;
|
408
|
+
end
|
409
|
+
class SizeRequired < FileError;
|
410
|
+
end
|
411
|
+
class ToPathRequired < FileError;
|
412
|
+
end
|
413
|
+
class FileVersionMissingOrIncorrect < FileError;
|
414
|
+
end
|
415
|
+
|
416
|
+
# Enpoint Errors
|
417
|
+
|
418
|
+
class InvalidPath < EndpointError;
|
419
|
+
end
|
420
|
+
class AlreadyExists < EndpointError;
|
421
|
+
end
|
422
|
+
class NotAllowed < EndpointError;
|
423
|
+
end
|
424
|
+
|
425
|
+
# Raises specific exception mapped by CloudFS error code in json message
|
426
|
+
#
|
427
|
+
# @param error [ServerError] contains message, request, response context
|
428
|
+
# and http code returned by cloudfs service
|
429
|
+
#
|
430
|
+
# @raise [ServiceError] mapped by code in message parameter in {ServerError}
|
431
|
+
def self.raise_service_error(error)
|
432
|
+
begin
|
433
|
+
hash = Utils.json_to_hash(error.message)
|
434
|
+
rescue StandardError
|
435
|
+
raise ServiceError.new(error.message, error)
|
436
|
+
end
|
437
|
+
raise ServiceError.new(error.message, error) unless hash.key?(:error)
|
438
|
+
|
439
|
+
if hash[:error].is_a?(Hash)
|
440
|
+
code, message = Utils.hash_to_arguments(hash[:error],
|
441
|
+
:code, :message)
|
442
|
+
else
|
443
|
+
message = hash.fetch(:message) { hash[:error] }
|
444
|
+
code = hash.fetch(:error_code, nil)
|
445
|
+
end
|
446
|
+
raise ServiceError.new(message, error) unless code && BITCASA_ERRORS.key?(code)
|
447
|
+
raise const_get(BITCASA_ERRORS[code]).new(message, error)
|
448
|
+
end
|
449
|
+
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|