files.com 1.0.92

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.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTORS +4 -0
  3. data/Gemfile +12 -0
  4. data/Gemfile.lock +82 -0
  5. data/LICENSE +21 -0
  6. data/README.md +119 -0
  7. data/Rakefile +12 -0
  8. data/SECURITY.md +24 -0
  9. data/_VERSION +1 -0
  10. data/bin/files +8 -0
  11. data/bin/files-console +16 -0
  12. data/docs/account_line_item.md +41 -0
  13. data/docs/action.md +37 -0
  14. data/docs/api_key.md +202 -0
  15. data/docs/app.md +59 -0
  16. data/docs/as2_key.md +133 -0
  17. data/docs/auto.md +11 -0
  18. data/docs/automation.md +190 -0
  19. data/docs/behavior.md +208 -0
  20. data/docs/bundle.md +252 -0
  21. data/docs/bundle_download.md +35 -0
  22. data/docs/clickwrap.md +143 -0
  23. data/docs/dns_record.md +35 -0
  24. data/docs/errors.md +17 -0
  25. data/docs/file.md +204 -0
  26. data/docs/file_action.md +126 -0
  27. data/docs/file_comment.md +116 -0
  28. data/docs/file_comment_reaction.md +62 -0
  29. data/docs/file_part_upload.md +37 -0
  30. data/docs/file_utils.md +4 -0
  31. data/docs/folder.md +90 -0
  32. data/docs/group.md +153 -0
  33. data/docs/group_user.md +124 -0
  34. data/docs/history.md +171 -0
  35. data/docs/history_export.md +174 -0
  36. data/docs/image.md +13 -0
  37. data/docs/invoice.md +72 -0
  38. data/docs/invoice_line_item.md +27 -0
  39. data/docs/ip_address.md +55 -0
  40. data/docs/lock.md +98 -0
  41. data/docs/message.md +147 -0
  42. data/docs/message_comment.md +132 -0
  43. data/docs/message_comment_reaction.md +94 -0
  44. data/docs/message_reaction.md +94 -0
  45. data/docs/notification.md +177 -0
  46. data/docs/payment.md +72 -0
  47. data/docs/payment_line_item.md +19 -0
  48. data/docs/permission.md +95 -0
  49. data/docs/preview.md +19 -0
  50. data/docs/project.md +121 -0
  51. data/docs/public_ip_address.md +13 -0
  52. data/docs/public_key.md +133 -0
  53. data/docs/remote_server.md +356 -0
  54. data/docs/request.md +100 -0
  55. data/docs/session.md +78 -0
  56. data/docs/site.md +448 -0
  57. data/docs/sso_strategy.md +114 -0
  58. data/docs/status.md +21 -0
  59. data/docs/style.md +93 -0
  60. data/docs/usage_daily_snapshot.md +45 -0
  61. data/docs/usage_snapshot.md +53 -0
  62. data/docs/user.md +535 -0
  63. data/docs/user_cipher_use.md +41 -0
  64. data/docs/user_request.md +93 -0
  65. data/files.com.gemspec +22 -0
  66. data/lib/files.com.rb +184 -0
  67. data/lib/files.com/api.rb +38 -0
  68. data/lib/files.com/api_client.rb +340 -0
  69. data/lib/files.com/errors.rb +41 -0
  70. data/lib/files.com/list.rb +95 -0
  71. data/lib/files.com/models/account_line_item.rb +82 -0
  72. data/lib/files.com/models/action.rb +77 -0
  73. data/lib/files.com/models/api_key.rb +270 -0
  74. data/lib/files.com/models/app.rb +106 -0
  75. data/lib/files.com/models/as2_key.rb +179 -0
  76. data/lib/files.com/models/auto.rb +17 -0
  77. data/lib/files.com/models/automation.rb +304 -0
  78. data/lib/files.com/models/behavior.rb +266 -0
  79. data/lib/files.com/models/bundle.rb +371 -0
  80. data/lib/files.com/models/bundle_download.rb +49 -0
  81. data/lib/files.com/models/clickwrap.rb +197 -0
  82. data/lib/files.com/models/dir.rb +3 -0
  83. data/lib/files.com/models/dns_record.rb +51 -0
  84. data/lib/files.com/models/errors.rb +22 -0
  85. data/lib/files.com/models/file.rb +968 -0
  86. data/lib/files.com/models/file_action.rb +126 -0
  87. data/lib/files.com/models/file_comment.rb +146 -0
  88. data/lib/files.com/models/file_comment_reaction.rb +100 -0
  89. data/lib/files.com/models/file_part_upload.rb +82 -0
  90. data/lib/files.com/models/file_utils.rb +118 -0
  91. data/lib/files.com/models/folder.rb +357 -0
  92. data/lib/files.com/models/group.rb +208 -0
  93. data/lib/files.com/models/group_user.rb +171 -0
  94. data/lib/files.com/models/history.rb +228 -0
  95. data/lib/files.com/models/history_export.rb +353 -0
  96. data/lib/files.com/models/image.rb +22 -0
  97. data/lib/files.com/models/invoice.rb +117 -0
  98. data/lib/files.com/models/invoice_line_item.rb +57 -0
  99. data/lib/files.com/models/ip_address.rb +66 -0
  100. data/lib/files.com/models/lock.rb +173 -0
  101. data/lib/files.com/models/message.rb +201 -0
  102. data/lib/files.com/models/message_comment.rb +165 -0
  103. data/lib/files.com/models/message_comment_reaction.rb +128 -0
  104. data/lib/files.com/models/message_reaction.rb +128 -0
  105. data/lib/files.com/models/notification.rb +263 -0
  106. data/lib/files.com/models/payment.rb +117 -0
  107. data/lib/files.com/models/payment_line_item.rb +37 -0
  108. data/lib/files.com/models/permission.rb +172 -0
  109. data/lib/files.com/models/preview.rb +37 -0
  110. data/lib/files.com/models/project.rb +140 -0
  111. data/lib/files.com/models/public_ip_address.rb +22 -0
  112. data/lib/files.com/models/public_key.rb +179 -0
  113. data/lib/files.com/models/remote_server.rb +680 -0
  114. data/lib/files.com/models/request.rb +179 -0
  115. data/lib/files.com/models/session.rb +247 -0
  116. data/lib/files.com/models/site.rb +733 -0
  117. data/lib/files.com/models/sso_strategy.rb +227 -0
  118. data/lib/files.com/models/status.rb +37 -0
  119. data/lib/files.com/models/style.rb +131 -0
  120. data/lib/files.com/models/usage_daily_snapshot.rb +66 -0
  121. data/lib/files.com/models/usage_snapshot.rb +96 -0
  122. data/lib/files.com/models/user.rb +876 -0
  123. data/lib/files.com/models/user_cipher_use.rb +63 -0
  124. data/lib/files.com/models/user_request.rb +127 -0
  125. data/lib/files.com/response.rb +25 -0
  126. data/lib/files.com/sizable_io.rb +32 -0
  127. data/lib/files.com/system_profiler.rb +56 -0
  128. data/lib/files.com/util.rb +106 -0
  129. data/lib/files.com/version.rb +5 -0
  130. data/spec/list_spec.rb +214 -0
  131. data/spec/models/file_spec.rb +68 -0
  132. data/spec/models/folder_spec.rb +40 -0
  133. data/spec/spec_helper.rb +36 -0
  134. data/test.sh +8 -0
  135. data/test/test.rb +75 -0
  136. metadata +235 -0
@@ -0,0 +1,41 @@
1
+ # UserCipherUse
2
+
3
+ ## Example UserCipherUse Object
4
+
5
+ ```
6
+ {
7
+ "id": 1,
8
+ "protocol_cipher": "TLSv1.2; ECDHE-RSA-AES256-GCM-SHA384",
9
+ "created_at": "2000-01-01T01:00:00Z",
10
+ "interface": "restapi",
11
+ "updated_at": "2000-01-01T01:00:00Z",
12
+ "user_id": 1
13
+ }
14
+ ```
15
+
16
+ * `id` (int64): UserCipherUse ID
17
+ * `protocol_cipher` (string): The protocol and cipher employed
18
+ * `created_at` (date-time): The earliest recorded use of this combination of interface and protocol and cipher (for this user)
19
+ * `interface` (string): The interface accessed
20
+ * `updated_at` (date-time): The most recent use of this combination of interface and protocol and cipher (for this user)
21
+ * `user_id` (int64): ID of the user who performed this access
22
+
23
+
24
+ ---
25
+
26
+ ## List User Cipher Uses
27
+
28
+ ```
29
+ Files::UserCipherUse.list(
30
+ user_id: 1,
31
+ page: 1,
32
+ per_page: 1
33
+ )
34
+ ```
35
+
36
+ ### Parameters
37
+
38
+ * `user_id` (int64): User ID. Provide a value of `0` to operate the current session's user.
39
+ * `page` (int64): Current page number.
40
+ * `per_page` (int64): Number of records to show per page. (Max: 10,000, 1,000 or less is recommended).
41
+ * `action` (string): Deprecated: If set to `count` returns a count of matching records rather than the records themselves.
@@ -0,0 +1,93 @@
1
+ # UserRequest
2
+
3
+ ## Example UserRequest Object
4
+
5
+ ```
6
+ {
7
+ "name": "John Doe",
8
+ "email": "john.doe@files.com",
9
+ "details": "Changed Departments"
10
+ }
11
+ ```
12
+
13
+ * `name` (string): User's full name
14
+ * `email` (email): User email address
15
+ * `details` (string): Details of the user's request
16
+
17
+
18
+ ---
19
+
20
+ ## List User Requests
21
+
22
+ ```
23
+ Files::UserRequest.list(
24
+ page: 1,
25
+ per_page: 1
26
+ )
27
+ ```
28
+
29
+ ### Parameters
30
+
31
+ * `page` (int64): Current page number.
32
+ * `per_page` (int64): Number of records to show per page. (Max: 10,000, 1,000 or less is recommended).
33
+ * `action` (string): Deprecated: If set to `count` returns a count of matching records rather than the records themselves.
34
+
35
+
36
+ ---
37
+
38
+ ## Show User Request
39
+
40
+ ```
41
+ Files::UserRequest.find(id)
42
+ ```
43
+
44
+ ### Parameters
45
+
46
+ * `id` (int64): Required - User Request ID.
47
+
48
+
49
+ ---
50
+
51
+ ## Create User Request
52
+
53
+ ```
54
+ Files::UserRequest.create(
55
+ name: "name",
56
+ email: "email",
57
+ details: "details"
58
+ )
59
+ ```
60
+
61
+ ### Parameters
62
+
63
+ * `name` (string): Required - Name of user requested
64
+ * `email` (string): Required - Email of user requested
65
+ * `details` (string): Required - Details of the user request
66
+
67
+
68
+ ---
69
+
70
+ ## Delete User Request
71
+
72
+ ```
73
+ Files::UserRequest.delete(id)
74
+ ```
75
+
76
+ ### Parameters
77
+
78
+ * `id` (int64): Required - User Request ID.
79
+
80
+
81
+ ---
82
+
83
+ ## Delete User Request
84
+
85
+ ```
86
+ user_request = Files::UserRequest.list_for(path).first
87
+
88
+ user_request.delete
89
+ ```
90
+
91
+ ### Parameters
92
+
93
+ * `id` (int64): Required - User Request ID.
@@ -0,0 +1,22 @@
1
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "files.com"
5
+ s.version = File.open(File.expand_path('_VERSION', __dir__)).read
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = [ "files.com" ]
8
+ s.email = [ "support@files.com" ]
9
+ s.homepage = "https://www.files.com"
10
+ s.summary = "Files.com Ruby client."
11
+ s.description = "The Files.com Ruby client."
12
+ s.license = "MIT"
13
+ s.required_ruby_version = ">= 2.5"
14
+ s.add_dependency 'addressable', ">= 2.7.0"
15
+ s.add_dependency 'concurrent-ruby', ">= 1.1.3"
16
+ s.add_dependency 'faraday', ">= 1.0.1"
17
+ s.add_dependency 'net-http-persistent'
18
+
19
+ s.files = `find *`.split("\n").uniq.sort.reject(&:empty?)
20
+ s.executables = [ "files", "files-console" ]
21
+ s.require_paths = [ "lib" ]
22
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+ require "faraday"
5
+ require "json"
6
+ require "logger"
7
+ require "openssl"
8
+ require "rbconfig"
9
+ require "securerandom"
10
+ require "set"
11
+ require "socket"
12
+ require "uri"
13
+ require "addressable/uri"
14
+ require "concurrent/promise"
15
+
16
+ $LOAD_PATH.push __dir__
17
+
18
+ require "files.com/version"
19
+
20
+ require "files.com/sizable_io"
21
+ require "files.com/api"
22
+ require "files.com/api_client"
23
+ require "files.com/errors"
24
+ require "files.com/response"
25
+ require "files.com/system_profiler"
26
+ require "files.com/util"
27
+ require "files.com/list"
28
+
29
+ require "files.com/models/account_line_item"
30
+ require "files.com/models/action"
31
+ require "files.com/models/api_key"
32
+ require "files.com/models/app"
33
+ require "files.com/models/as2_key"
34
+ require "files.com/models/auto"
35
+ require "files.com/models/automation"
36
+ require "files.com/models/behavior"
37
+ require "files.com/models/bundle"
38
+ require "files.com/models/bundle_download"
39
+ require "files.com/models/clickwrap"
40
+ require "files.com/models/dns_record"
41
+ require "files.com/models/errors"
42
+ require "files.com/models/file"
43
+ require "files.com/models/file_action"
44
+ require "files.com/models/file_comment"
45
+ require "files.com/models/file_comment_reaction"
46
+ require "files.com/models/file_part_upload"
47
+ require "files.com/models/folder"
48
+ require "files.com/models/group"
49
+ require "files.com/models/group_user"
50
+ require "files.com/models/history"
51
+ require "files.com/models/history_export"
52
+ require "files.com/models/image"
53
+ require "files.com/models/invoice"
54
+ require "files.com/models/invoice_line_item"
55
+ require "files.com/models/ip_address"
56
+ require "files.com/models/lock"
57
+ require "files.com/models/message"
58
+ require "files.com/models/message_comment"
59
+ require "files.com/models/message_comment_reaction"
60
+ require "files.com/models/message_reaction"
61
+ require "files.com/models/notification"
62
+ require "files.com/models/payment"
63
+ require "files.com/models/payment_line_item"
64
+ require "files.com/models/permission"
65
+ require "files.com/models/preview"
66
+ require "files.com/models/project"
67
+ require "files.com/models/public_ip_address"
68
+ require "files.com/models/public_key"
69
+ require "files.com/models/remote_server"
70
+ require "files.com/models/request"
71
+ require "files.com/models/session"
72
+ require "files.com/models/site"
73
+ require "files.com/models/sso_strategy"
74
+ require "files.com/models/status"
75
+ require "files.com/models/style"
76
+ require "files.com/models/usage_daily_snapshot"
77
+ require "files.com/models/usage_snapshot"
78
+ require "files.com/models/user"
79
+ require "files.com/models/user_cipher_use"
80
+ require "files.com/models/user_request"
81
+
82
+ require "files.com/models/dir"
83
+ require "files.com/models/file_utils"
84
+
85
+ module Files
86
+ @api_key = nil
87
+ @app_info = nil
88
+ @base_url = "https://app.files.com"
89
+ @log_level = nil
90
+ @logger = nil
91
+ @proxy = nil
92
+ @session_id = nil
93
+
94
+ @max_network_retries = 3
95
+ @max_network_retry_delay = 2
96
+ @initial_network_retry_delay = 0.5
97
+
98
+ @open_timeout = 30
99
+ @read_timeout = 80
100
+
101
+ class << self
102
+ attr_accessor :api_key, :base_url, :initial_network_retry_delay, :max_network_retry_delay, :open_timeout, :read_timeout, :proxy, :session_id
103
+ end
104
+
105
+ # map to the same values as the standard library's logger
106
+ LEVEL_DEBUG = Logger::DEBUG
107
+ LEVEL_ERROR = Logger::ERROR
108
+ LEVEL_INFO = Logger::INFO
109
+
110
+ # When set prompts the library to log some extra information to $stdout and
111
+ # $stderr about what it's doing. For example, it'll produce information about
112
+ # requests, responses, and errors that are received. Valid log levels are
113
+ # `debug` and `info`, with `debug` being a little more verbose in places.
114
+ #
115
+ # Use of this configuration is only useful when `.logger` is _not_ set. When
116
+ # it is, the decision what levels to print is entirely deferred to the logger.
117
+ def self.log_level
118
+ @log_level
119
+ end
120
+
121
+ def self.log_level=(val)
122
+ # Backwards compatibility for values that we briefly allowed
123
+ if val == "debug"
124
+ val = LEVEL_DEBUG
125
+ elsif val == "info"
126
+ val = LEVEL_INFO
127
+ end
128
+
129
+ if !val.nil? && ![ LEVEL_DEBUG, LEVEL_ERROR, LEVEL_INFO ].include?(val)
130
+ raise ArgumentError,
131
+ "log_level should only be set to `nil`, `debug` or `info`"
132
+ end
133
+ @log_level = val
134
+ end
135
+
136
+ # Sets a logger to which logging output will be sent. The logger should
137
+ # support the same interface as the `Logger` class that's part of Ruby's
138
+ # standard library (hint, anything in `Rails.logger` will likely be
139
+ # suitable).
140
+ #
141
+ # If `.logger` is set, the value of `.log_level` is ignored. The decision on
142
+ # what levels to print is entirely deferred to the logger.
143
+ def self.logger
144
+ @logger
145
+ end
146
+
147
+ def self.logger=(val)
148
+ @logger = val
149
+ end
150
+
151
+ def self.max_network_retries
152
+ @max_network_retries
153
+ end
154
+
155
+ def self.max_network_retries=(val)
156
+ @max_network_retries = val.to_i
157
+ end
158
+
159
+ def self.session=(session)
160
+ session.save unless session.id
161
+ self.session_id = session.id
162
+ end
163
+
164
+ # Sets some basic information about the running application that's sent along
165
+ # with API requests.
166
+ #
167
+ # Takes a name and optional partner program ID, plugin URL, and version.
168
+ def self.set_app_info(name, partner_id: nil, url: nil, version: nil)
169
+ @app_info = {
170
+ name: name,
171
+ partner_id: partner_id,
172
+ url: url,
173
+ version: version,
174
+ }
175
+ end
176
+
177
+ def self.app_info
178
+ @app_info
179
+ end
180
+
181
+ def self.app_info=(info)
182
+ @app_info = info
183
+ end
184
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Files
4
+ class Api
5
+ def self.send_request(path, verb, params, options)
6
+ warn_on_options_in_params(params)
7
+
8
+ options[:client] ||= ApiClient.active_client
9
+
10
+ headers = options.clone
11
+ api_key = headers.delete(:api_key)
12
+ client = headers.delete(:client)
13
+ session_id = headers.delete(:session_id)
14
+ if session = headers.delete(:session)
15
+ session.save unless session.id
16
+ session_id = session.id
17
+ end
18
+
19
+ resp, options[:api_key], options[:session_id] = client.execute_request(
20
+ verb, path, api_key: api_key, headers: headers, params: params, session_id: session_id
21
+ )
22
+
23
+ # Hash#select returns an array before 1.9
24
+ options_to_persist = {}
25
+ options.each do |k, v|
26
+ options_to_persist[k] = v if Util::OPTS.include?(k)
27
+ end
28
+
29
+ [ resp, options_to_persist ]
30
+ end
31
+
32
+ def self.warn_on_options_in_params(params)
33
+ Util::OPTS.each do |opt|
34
+ warn("WARNING: #{opt} should be in the options hash, not the params hash. You may need to create a second hash that goes after params.)") if params.key?(opt)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,340 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Files
4
+ class ApiClient
5
+ attr_accessor :conn
6
+
7
+ def initialize(conn = nil)
8
+ self.conn = conn || self.class.default_conn
9
+ @system_profiler = SystemProfiler.new
10
+ @last_request_metrics = nil
11
+ end
12
+
13
+ def self.active_client
14
+ Thread.current[:files_api_client] || default_client
15
+ end
16
+
17
+ # net_http_persistent does not support streaming downloads with faraday when directly downloading from S3
18
+ # falling back to net_http.
19
+ def self.download_client
20
+ Thread.current[:files_api_client_download_client] ||= ApiClient.new(download_conn)
21
+ end
22
+
23
+ def self.download_conn
24
+ Thread.current[:files_api_client_download_conn] ||= build_default_conn(force_net_http: true)
25
+ end
26
+
27
+ def self.default_client
28
+ Thread.current[:files_api_client_default_client] ||= ApiClient.new(default_conn)
29
+ end
30
+
31
+ def self.default_conn
32
+ Thread.current[:files_api_client_default_conn] ||= build_default_conn
33
+ end
34
+
35
+ def self.build_default_conn(force_net_http: false)
36
+ conn = Faraday.new do |builder|
37
+ builder.use Faraday::Request::Multipart
38
+ builder.use Faraday::Request::UrlEncoded
39
+ builder.use Faraday::Response::RaiseError
40
+
41
+ if Gem.win_platform? || RUBY_PLATFORM == "java" || force_net_http
42
+ builder.adapter :net_http
43
+ else
44
+ builder.adapter :net_http_persistent
45
+ end
46
+ end
47
+
48
+ conn.proxy = Files.proxy if Files.proxy
49
+ conn.ssl.verify = true
50
+
51
+ conn
52
+ end
53
+
54
+ def self.should_retry?(error, num_retries)
55
+ return false if num_retries >= Files.max_network_retries
56
+ return true if error.is_a?(Faraday::TimeoutError)
57
+ return true if error.is_a?(Faraday::ConnectionFailed)
58
+
59
+ false
60
+ end
61
+
62
+ def self.sleep_time(num_retries)
63
+ sleep_seconds = [
64
+ Files.initial_network_retry_delay * (2**(num_retries - 1)),
65
+ Files.max_network_retry_delay
66
+ ].min
67
+ sleep_seconds *= (0.5 * (1 + rand))
68
+ [ Files.initial_network_retry_delay, sleep_seconds ].max
69
+ end
70
+
71
+ def request
72
+ @last_response = nil
73
+ old_files_api_client = Thread.current[:files_api_client]
74
+ Thread.current[:files_api_client] = self
75
+
76
+ begin
77
+ res = yield
78
+ [ res, @last_response ]
79
+ ensure
80
+ Thread.current[:files_api_client] = old_files_api_client
81
+ end
82
+ end
83
+
84
+ def execute_request(method, path, base_url: nil, api_key: nil, session_id: nil, headers: {}, params: {})
85
+ base_url ||= Files.base_url
86
+ session_id ||= Files.session_id
87
+
88
+ if session_id and session_id != ""
89
+ check_session_id!(session_id)
90
+ elsif path !~ /^\/sessions/ # TODO: automate this to refer to any unauthenticated endpoint
91
+ api_key ||= Files.api_key
92
+ check_api_key!(api_key)
93
+ end
94
+
95
+ body = nil
96
+ query_params = nil
97
+ case method.to_s.downcase.to_sym
98
+ when :get, :head, :delete
99
+ query_params = params
100
+ else
101
+ body = params
102
+ end
103
+
104
+ headers = request_headers(api_key, session_id, method).update(headers)
105
+ url = api_url(path, base_url)
106
+
107
+ context = RequestLogContext.new
108
+ context.api_key = api_key
109
+ context.body = body
110
+ context.method = method
111
+ context.path = path
112
+ context.query_params = query_params if query_params
113
+ context.session_id = session_id
114
+
115
+ http_resp = execute_request_with_rescues(base_url, context) do
116
+ conn.run_request(method, url, body, headers) do |req|
117
+ req.options.open_timeout = Files.open_timeout
118
+ req.options.timeout = Files.read_timeout
119
+ req.params = query_params unless query_params.nil?
120
+ end
121
+ end
122
+
123
+ begin
124
+ resp = Response.from_faraday_response(http_resp)
125
+ rescue JSON::ParserError
126
+ raise general_api_error(http_resp.status, http_resp.body)
127
+ end
128
+
129
+ @last_response = resp
130
+ [ resp, api_key, session_id ]
131
+ end
132
+
133
+ def remote_request(method, url, headers = {}, body = nil)
134
+ context = RequestLogContext.new
135
+ context.method = method
136
+ context.path = url
137
+
138
+ execute_request_with_rescues(Files.base_url, context, true) do
139
+ conn.run_request(method, url, body, headers) do |req|
140
+ req.options.open_timeout = Files.open_timeout
141
+ req.options.timeout = Files.read_timeout
142
+ yield(req) if block_given?
143
+ end
144
+ end
145
+ end
146
+
147
+ def stream_download(uri, io)
148
+ if conn.adapter == Faraday::Adapter::NetHttp
149
+ uri = URI(uri)
150
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
151
+ request = Net::HTTP::Get.new uri
152
+ http.request request do |response|
153
+ io.fulfill_content_length(response.content_length) if io.respond_to?(:fulfill_content_length)
154
+ response.read_body do |chunk|
155
+ io << chunk.encode!
156
+ end
157
+ end
158
+ end
159
+ else
160
+ response = remote_request(:get, uri)
161
+ io.fulfill_content_length(response.content_length) if io.respond_to?(:fulfill_content_length)
162
+ io.write(response.body)
163
+ end
164
+ end
165
+
166
+ def cursor
167
+ @last_response.http_headers["x-files-cursor"]
168
+ end
169
+
170
+ private def api_url(url = "", base_url = nil)
171
+ (base_url || Files.base_url) + "/api/rest/v1" + url
172
+ end
173
+
174
+ private def check_api_key!(api_key)
175
+ unless api_key
176
+ raise AuthenticationError, "No Files.com API key provided. " \
177
+ 'Set your API key using "Files.api_key = <API-KEY>". ' \
178
+ "You can generate API keys from the Files.com's web interface. "
179
+ end
180
+
181
+ return unless api_key =~ /\s/
182
+
183
+ raise AuthenticationError, "Your API key is invalid (it contains whitespace)"
184
+ end
185
+
186
+ private def check_session_id!(session_id)
187
+ return unless session_id =~ /\s/
188
+
189
+ raise AuthenticationError, "The provided Session ID is invalid (it contains whitespace)"
190
+ end
191
+
192
+ def execute_request_with_rescues(base_url, context, skip_body_logging = false)
193
+ num_retries = 0
194
+ begin
195
+ request_start = Time.now
196
+ log_request(context, num_retries, skip_body_logging)
197
+ resp = yield
198
+ log_response(context, request_start, resp.status, resp.body, skip_body_logging)
199
+ rescue StandardError => e
200
+ error_context = context
201
+
202
+ if e.respond_to?(:response) && e.response
203
+ error_context = context
204
+ log_response(error_context, request_start,
205
+ e.response[:status], e.response[:body], skip_body_logging
206
+ )
207
+ else
208
+ log_response_error(error_context, request_start, e)
209
+ end
210
+
211
+ if self.class.should_retry?(e, num_retries)
212
+ num_retries += 1
213
+ sleep self.class.sleep_time(num_retries)
214
+ retry
215
+ end
216
+
217
+ case e
218
+ when Faraday::ClientError
219
+ if e.response
220
+ handle_error_response(e.response, error_context)
221
+ else
222
+ handle_network_error(e, error_context, num_retries, base_url)
223
+ end
224
+
225
+ else
226
+ raise
227
+ end
228
+ end
229
+
230
+ resp
231
+ end
232
+
233
+ private def general_api_error(status, body)
234
+ APIError.new("Unexpected response object from API: #{body.inspect} (HTTP response code was #{status})", http_status: status, http_body: body)
235
+ end
236
+
237
+ private def format_app_info(info)
238
+ str = info[:name]
239
+ str = "#{str}/#{info[:version]}" unless info[:version].nil?
240
+ str = "#{str} (#{info[:url]})" unless info[:url].nil?
241
+ str
242
+ end
243
+
244
+ private def handle_error_response(http_resp, context)
245
+ begin
246
+ resp = Response.from_faraday_hash(http_resp)
247
+ error_data = resp.data[:error] || resp.data[:errors]
248
+ error_data = error_data.first if error_data.is_a?(Array)
249
+ error_data = { message: error_data } if error_data.is_a?(String)
250
+
251
+ raise Error, "Unknown error" unless error_data
252
+ rescue JSON::ParserError, Error
253
+ raise general_api_error(http_resp[:status], http_resp[:body])
254
+ end
255
+
256
+ error = specific_api_error(resp, error_data, context)
257
+
258
+ error.response = resp
259
+ raise(error)
260
+ end
261
+
262
+ private def specific_api_error(resp, error_data, _context)
263
+ Util.log_error("API error", status: resp.http_status, error_message: error_data[:message])
264
+
265
+ opts = {
266
+ http_body: resp.http_body,
267
+ http_headers: resp.http_headers,
268
+ http_status: resp.http_status,
269
+ json_body: resp.data,
270
+ code: error_data[:code] || resp.http_status,
271
+ }
272
+
273
+ case resp.http_status
274
+ when 400, 404
275
+ InvalidRequestError.new(error_data[:message], opts)
276
+ when 401
277
+ AuthenticationError.new(error_data[:message], opts)
278
+ when 403
279
+ PermissionError.new(error_data[:message], opts)
280
+ when 429
281
+ RateLimitError.new(error_data[:message], opts)
282
+ else
283
+ APIError.new(error_data[:message], opts)
284
+ end
285
+ end
286
+
287
+ private def handle_network_error(error, context, num_retries, base_url = nil)
288
+ base_url ||= Files.base_url
289
+
290
+ Util.log_error("Network error", error_message: error.message, request_id: context.request_id)
291
+ message = "Could not connect to Files.com at URL #{base_url}. Please check your internet connection and try again. If this problem persists, you should check Files.com's service status at https://status.files.com, or contact your primary account representative."
292
+ message += " Request was retried #{num_retries} times." if num_retries > 0
293
+ message += "\n\n(Network error: #{error.message})"
294
+
295
+ raise APIConnectionError, message
296
+ end
297
+
298
+ private def request_headers(api_key, session_id, _method)
299
+ user_agent = "Files.com Ruby SDK v#{Files::VERSION}"
300
+ user_agent += " " + format_app_info(Files.app_info) unless Files.app_info.nil?
301
+
302
+ headers = {
303
+ "User-Agent" => user_agent,
304
+ "Content-Type" => "application/x-www-form-urlencoded",
305
+ }
306
+ headers["X-FilesAPI-Key"] = api_key if api_key
307
+ headers["X-FilesAPI-Auth"] = session_id if session_id
308
+
309
+ user_agent = @system_profiler.user_agent
310
+ begin
311
+ headers.update("X-Files-Client-User-Agent" => JSON.generate(user_agent))
312
+ rescue StandardError => e
313
+ headers.update(
314
+ "X-Files-Client-Raw-User-Agent" => user_agent.inspect,
315
+ error: "#{e} (#{e.class})"
316
+ )
317
+ end
318
+
319
+ headers
320
+ end
321
+
322
+ private def log_request(context, num_retries, no_body = false)
323
+ Util.log_info("Request", method: context.method, num_retries: num_retries, path: context.path)
324
+ Util.log_debug("Request details", body: context.body, query_params: context.query_params) unless no_body
325
+ end
326
+
327
+ private def log_response(context, request_start, status, body, no_body = false)
328
+ Util.log_info("Response", elapsed: Time.now - request_start, method: context.method, path: context.path, status: status)
329
+ Util.log_debug("Response details", body: body) unless no_body
330
+ end
331
+
332
+ private def log_response_error(context, request_start, error)
333
+ Util.log_error("Error", elapsed: Time.now - request_start, error_message: error.message, method: context.method, path: context.path)
334
+ end
335
+
336
+ class RequestLogContext
337
+ attr_accessor :body, :api_key, :method, :path, :query_params, :session_id
338
+ end
339
+ end
340
+ end