cloudfs 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
+ -
@@ -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