cloudfs 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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