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.
- 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
|