fileboy 0.1.1 → 0.2.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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +49 -1
- data/lib/fileboy/client.rb +223 -2
- data/lib/fileboy/file_data.rb +84 -3
- data/lib/fileboy/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f032a5828ce3db69a2ac9a2952774a2d5797906ee46c144af088f5bba1188a1e
|
|
4
|
+
data.tar.gz: b625e71b7d308b922f4fd5ed9bd9821058d4a992732baab9cd64b2cfc8d5e245
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 35732881e4adc1331d672f708e30618fc112e5f306093eec6cf27f0edc779a29617ee7124da5d2c7ff4f7abd431673757a85b585c0a1f4662de1e5f148af0ed9
|
|
7
|
+
data.tar.gz: fb4739be49a85cd9645a20d6bed26e674b02e50c6e8e53c71594ec4bdbd102799e1c106d98e53d9d327ccf36efe91e2087038cb5c761f1583893b7b01cba2870
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- New: manage your app from Ruby. List, change and delete files, videos and labels, and read your usage. These calls need the private key and raise `Fileboy::ConfigurationError` before any request if the client has none.
|
|
6
|
+
- `list_files` and `list_videos` return a `Fileboy::Page` with `items` and `next_cursor`. `each_file` and `each_video` follow every page for you and can be used lazily.
|
|
7
|
+
- `update_file` changes a file's name, type, metadata (merged by default, `replace_metadata: true` to replace) and labels. `delete_files` removes up to 100 files at once.
|
|
8
|
+
- `Fileboy::FileData` has a new `labels` list, filled by the management calls.
|
|
9
|
+
- Errors from these calls use the existing classes (`NotFoundError`, `AuthenticationError`, `PermissionError`, `BadRequestError`, `ServerError`) and keep the API's `code`, message and HTTP `status`.
|
|
10
|
+
|
|
3
11
|
## 0.1.1
|
|
4
12
|
|
|
5
13
|
- Fix: the gem failed to load on Ruby 3.4 because it required the `base64` gem, which is no longer a default gem. It now uses only the standard library.
|
data/README.md
CHANGED
|
@@ -78,6 +78,54 @@ created in the dashboard.
|
|
|
78
78
|
|
|
79
79
|
You can also pass a hash of `name => value`, such as `{ resize: "300x_", format: "webp", quality: 75 }`, or a ready-made string.
|
|
80
80
|
|
|
81
|
+
## Managing files
|
|
82
|
+
|
|
83
|
+
List, change and delete what is stored in your app. These calls are signed, so build the client with
|
|
84
|
+
your private key. With only a public key they raise `Fileboy::ConfigurationError` before any request.
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
fileboy = Fileboy::Client.new(public_key: "PUBLIC", private_key: "PRIVATE")
|
|
88
|
+
|
|
89
|
+
# One page, with filters
|
|
90
|
+
page = fileboy.list_files(mime: "image/", metadata: { orderId: "42" }, limit: 100)
|
|
91
|
+
page.items.each { |file| puts "#{file.name} #{file.labels}" }
|
|
92
|
+
page.next_cursor # nil on the last page
|
|
93
|
+
|
|
94
|
+
# Every page, lazily
|
|
95
|
+
fileboy.each_file(mime: "image/").each { |file| puts file.name }
|
|
96
|
+
fileboy.each_file(label: "logo").first(5)
|
|
97
|
+
|
|
98
|
+
# Change a file. Metadata is merged and nil removes a key, replace_metadata: true replaces it.
|
|
99
|
+
fileboy.update_file(file.id, name: "new.png", metadata: { owner: "7", old: nil },
|
|
100
|
+
labels: { add: ["hero"], remove: ["draft"] })
|
|
101
|
+
fileboy.update_file("hero", name: "x") # a label name works too
|
|
102
|
+
|
|
103
|
+
fileboy.delete_file(file.id) # raises Fileboy::NotFoundError if it is gone
|
|
104
|
+
result = fileboy.delete_files([id1, id2]) # up to 100; result.deleted, .not_found, .failed
|
|
105
|
+
|
|
106
|
+
fileboy.list_videos(status: :ready).items
|
|
107
|
+
fileboy.update_video(video_id, name: "Launch.mp4")
|
|
108
|
+
fileboy.delete_video(video_id)
|
|
109
|
+
|
|
110
|
+
fileboy.list_labels # [Fileboy::Label]
|
|
111
|
+
fileboy.create_label("hero", file_id: file.id) # BadRequestError with code "label_taken" if it exists
|
|
112
|
+
fileboy.delete_label("hero") # the file is kept
|
|
113
|
+
|
|
114
|
+
usage = fileboy.fetch_usage
|
|
115
|
+
usage.files; usage.storage_bytes; usage.limits.storage_bytes # a nil limit means none is set
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Failures carry the API's `code`, message and HTTP `status`:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
begin
|
|
122
|
+
fileboy.delete_file(id)
|
|
123
|
+
rescue Fileboy::NotFoundError => e
|
|
124
|
+
e.code # "file_not_found"
|
|
125
|
+
e.status # 404
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
81
129
|
## Errors
|
|
82
130
|
|
|
83
131
|
Every error is a `Fileboy::Error` with `status`, `code` (the API's error code) and `body`.
|
|
@@ -97,7 +145,7 @@ Every error is a `Fileboy::Error` with `status`, `code` (the API's error code) a
|
|
|
97
145
|
|
|
98
146
|
* File, image and video reads need no authorization, so URL builders never sign anything.
|
|
99
147
|
* The shared `demo` key only accepts small images and cannot upload video.
|
|
100
|
-
*
|
|
148
|
+
* Managing files, videos and labels needs the private key. See below.
|
|
101
149
|
|
|
102
150
|
## Development
|
|
103
151
|
|
data/lib/fileboy/client.rb
CHANGED
|
@@ -16,8 +16,12 @@ module Fileboy
|
|
|
16
16
|
#
|
|
17
17
|
# Reading a file (GET /files/get, /images/get, video data) needs no authorization, so URL
|
|
18
18
|
# builders never sign anything. Uploads send your public key, or a signed token when you also
|
|
19
|
-
# pass a private key.
|
|
19
|
+
# pass a private key. Listing, changing and deleting files, videos and labels needs the private key.
|
|
20
20
|
class Client
|
|
21
|
+
HTTP_METHODS = {
|
|
22
|
+
get: Net::HTTP::Get, post: Net::HTTP::Post, patch: Net::HTTP::Patch, delete: Net::HTTP::Delete
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
21
25
|
attr_reader :public_key, :api_url
|
|
22
26
|
|
|
23
27
|
# @param public_key [String] your app's public key
|
|
@@ -184,6 +188,165 @@ module Fileboy
|
|
|
184
188
|
request(:get, "/videos/#{video_id}/poster", raw: true)
|
|
185
189
|
end
|
|
186
190
|
|
|
191
|
+
# --- Management (needs the private key) ----------------------------------------------------
|
|
192
|
+
#
|
|
193
|
+
# These list, change and delete what is stored in your app. They are signed with the private
|
|
194
|
+
# key, so build the client with `private_key:`. Without one they raise
|
|
195
|
+
# {Fileboy::ConfigurationError} before any request is made. Failures raise {Fileboy::NotFoundError},
|
|
196
|
+
# {Fileboy::AuthenticationError}, {Fileboy::PermissionError}, {Fileboy::BadRequestError} (invalid
|
|
197
|
+
# input, a label name that is taken) or {Fileboy::ServerError}, each with `status`, `code` and
|
|
198
|
+
# the API's message.
|
|
199
|
+
|
|
200
|
+
# One page of your files.
|
|
201
|
+
#
|
|
202
|
+
# page = fileboy.list_files(mime: "image/", limit: 100)
|
|
203
|
+
# page.items.each { |file| puts file.name }
|
|
204
|
+
# fileboy.list_files(cursor: page.next_cursor) if page.next_cursor
|
|
205
|
+
#
|
|
206
|
+
# @param limit [Integer, nil] page size, 1 to 100 (the API default is 50)
|
|
207
|
+
# @param cursor [String, nil] the `next_cursor` of the previous page
|
|
208
|
+
# @param sort [Symbol, String, nil] :created_at (default), :name or :file_size
|
|
209
|
+
# @param order [Symbol, String, nil] :asc or :desc
|
|
210
|
+
# @param label [String, nil] only the file this label points at
|
|
211
|
+
# @param mime [String, nil] only types starting with this, like "image/"
|
|
212
|
+
# @param name [String, nil] only names containing this text
|
|
213
|
+
# @param hash [String, nil] only files with this SHA1 content hash
|
|
214
|
+
# @param metadata [Hash, nil] only files whose metadata has all of these pairs
|
|
215
|
+
# @param created_before [Time, String, nil]
|
|
216
|
+
# @param created_after [Time, String, nil]
|
|
217
|
+
# @return [Fileboy::Page] of {Fileboy::FileData}
|
|
218
|
+
def list_files(limit: nil, cursor: nil, sort: nil, order: nil, label: nil, mime: nil, name: nil, hash: nil,
|
|
219
|
+
metadata: nil, created_before: nil, created_after: nil)
|
|
220
|
+
query = list_query(
|
|
221
|
+
limit: limit, cursor: cursor, sort: camelize(sort), order: order, label: label, mime: mime, name: name,
|
|
222
|
+
hash: hash, metadata: metadata, createdBefore: created_before, createdAfter: created_after
|
|
223
|
+
)
|
|
224
|
+
Page.from_api(manage(:get, "/files", query: query), FileData)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Every file matching the filters, following every page. Without a block it returns an
|
|
228
|
+
# Enumerator, so it can be lazy: `fileboy.each_file(mime: "image/").first(10)`.
|
|
229
|
+
#
|
|
230
|
+
# Takes the same filters as {#list_files}.
|
|
231
|
+
def each_file(**filters, &block)
|
|
232
|
+
return enum_for(:each_file, **filters) unless block
|
|
233
|
+
|
|
234
|
+
each_in_pages(:list_files, filters, &block)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Change a file. Only the fields you pass change. `metadata` is merged into the existing
|
|
238
|
+
# metadata and a nil value removes that key; pass `replace_metadata: true` to replace it all.
|
|
239
|
+
#
|
|
240
|
+
# fileboy.update_file(id, name: "new.png", metadata: { owner: "7", old: nil },
|
|
241
|
+
# labels: { add: ["hero"], remove: ["draft"] })
|
|
242
|
+
#
|
|
243
|
+
# @param id_or_label [String] a file id, or a label name
|
|
244
|
+
# @param labels [Hash, nil] `{ add: [...], remove: [...] }`. A label points at one file, so
|
|
245
|
+
# adding one that is on another file moves it here.
|
|
246
|
+
# @return [Fileboy::FileData]
|
|
247
|
+
def update_file(id_or_label, name: nil, mime: nil, metadata: nil, replace_metadata: nil, labels: nil)
|
|
248
|
+
body = {}
|
|
249
|
+
body[:name] = name unless name.nil?
|
|
250
|
+
body[:mime] = mime unless mime.nil?
|
|
251
|
+
unless metadata.nil?
|
|
252
|
+
body[:metadata] = metadata.to_h.each_with_object({}) { |(k, v), memo| memo[k.to_s] = v.nil? ? nil : v.to_s }
|
|
253
|
+
end
|
|
254
|
+
body[:replaceMetadata] = replace_metadata unless replace_metadata.nil?
|
|
255
|
+
unless labels.nil?
|
|
256
|
+
body[:labels] = {}
|
|
257
|
+
add = labels[:add] || labels["add"]
|
|
258
|
+
remove = labels[:remove] || labels["remove"]
|
|
259
|
+
body[:labels][:add] = Array(add).map(&:to_s) if add
|
|
260
|
+
body[:labels][:remove] = Array(remove).map(&:to_s) if remove
|
|
261
|
+
end
|
|
262
|
+
FileData.from_api(manage(:patch, "/files/#{file_ref(id_or_label)}", json: body).fetch("data"))
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Delete a file and its stored data. Raises {Fileboy::NotFoundError} when there is no such
|
|
266
|
+
# file, which includes one that is already deleted. Returns true.
|
|
267
|
+
def delete_file(id_or_label)
|
|
268
|
+
manage(:delete, "/files/#{file_ref(id_or_label)}")
|
|
269
|
+
true
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Delete up to 100 files at once. Unknown ids come back in `not_found` instead of raising, so
|
|
273
|
+
# it is safe to repeat. Ids in `failed` are still there because storage was unreachable, send
|
|
274
|
+
# them again.
|
|
275
|
+
#
|
|
276
|
+
# @param ids [Array<String>]
|
|
277
|
+
# @return [Fileboy::BulkDeleteResult]
|
|
278
|
+
def delete_files(ids)
|
|
279
|
+
ids = Array(ids).map(&:to_s)
|
|
280
|
+
raise InvalidArgumentError, "No ids given" if ids.empty?
|
|
281
|
+
|
|
282
|
+
BulkDeleteResult.from_api(manage(:post, "/files/delete", json: { ids: ids }).fetch("data"))
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# One page of your videos. `status` is :pending, :processing, :ready or :failed, `sort` is
|
|
286
|
+
# :created_at or :name. Other options are as in {#list_files}.
|
|
287
|
+
#
|
|
288
|
+
# @return [Fileboy::Page] of {Fileboy::ManagedVideo}
|
|
289
|
+
def list_videos(limit: nil, cursor: nil, sort: nil, order: nil, name: nil, status: nil, created_before: nil,
|
|
290
|
+
created_after: nil)
|
|
291
|
+
query = list_query(
|
|
292
|
+
limit: limit, cursor: cursor, sort: camelize(sort), order: order, name: name, status: status,
|
|
293
|
+
createdBefore: created_before, createdAfter: created_after
|
|
294
|
+
)
|
|
295
|
+
Page.from_api(manage(:get, "/videos", query: query), ManagedVideo)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Every video matching the filters, following every page. See {#each_file}.
|
|
299
|
+
def each_video(**filters, &block)
|
|
300
|
+
return enum_for(:each_video, **filters) unless block
|
|
301
|
+
|
|
302
|
+
each_in_pages(:list_videos, filters, &block)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Rename a video.
|
|
306
|
+
#
|
|
307
|
+
# @return [Fileboy::ManagedVideo]
|
|
308
|
+
def update_video(video_id, name:)
|
|
309
|
+
ManagedVideo.from_api(manage(:patch, "/videos/#{path_id(video_id)}", json: { name: name }).fetch("data"))
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Delete a video with its original, renditions, posters and transcripts. Returns true.
|
|
313
|
+
def delete_video(video_id)
|
|
314
|
+
manage(:delete, "/videos/#{path_id(video_id)}")
|
|
315
|
+
true
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# All labels of your app.
|
|
319
|
+
#
|
|
320
|
+
# @return [Array<Fileboy::Label>]
|
|
321
|
+
def list_labels
|
|
322
|
+
manage(:get, "/labels").fetch("data").map { |data| Label.from_api(data) }
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Create a label, optionally pointing at a file. Raises {Fileboy::BadRequestError} with code
|
|
326
|
+
# "label_taken" when the name exists.
|
|
327
|
+
#
|
|
328
|
+
# @return [Fileboy::Label]
|
|
329
|
+
def create_label(name, file_id: nil)
|
|
330
|
+
raise InvalidArgumentError, "No name given" if name.to_s.empty?
|
|
331
|
+
|
|
332
|
+
body = { name: name }
|
|
333
|
+
body[:fileId] = file_id if file_id
|
|
334
|
+
Label.from_api(manage(:post, "/labels", json: body).fetch("data"))
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Delete a label. The file it points at is kept. Returns true.
|
|
338
|
+
def delete_label(name)
|
|
339
|
+
manage(:delete, "/labels/#{path_id(name, "name")}")
|
|
340
|
+
true
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# How many files and videos the app has and how much they store, with the plan limits.
|
|
344
|
+
#
|
|
345
|
+
# @return [Fileboy::Usage]
|
|
346
|
+
def fetch_usage
|
|
347
|
+
Usage.from_api(manage(:get, "/usage").fetch("data"))
|
|
348
|
+
end
|
|
349
|
+
|
|
187
350
|
# --- Tokens --------------------------------------------------------------------------------
|
|
188
351
|
|
|
189
352
|
# A short-lived signed token for handing a browser limited access. Needs a private key.
|
|
@@ -207,6 +370,63 @@ module Fileboy
|
|
|
207
370
|
"#{@public_key}+#{ERB::Util.url_encode(value)}"
|
|
208
371
|
end
|
|
209
372
|
|
|
373
|
+
# Management calls are signed, so they need the private key.
|
|
374
|
+
def manage(method, path, query: nil, json: nil)
|
|
375
|
+
unless @private_key
|
|
376
|
+
raise ConfigurationError, "This needs the app's private key. Create the client with private_key:"
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
path += "?#{query}" if query && !query.empty?
|
|
380
|
+
request(method, path, json: json)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Query string from a hash. Skips nils, formats times as ISO 8601, expands metadata.
|
|
384
|
+
def list_query(options)
|
|
385
|
+
params = []
|
|
386
|
+
options.each do |key, value|
|
|
387
|
+
next if value.nil?
|
|
388
|
+
|
|
389
|
+
if key == :metadata
|
|
390
|
+
value.each { |k, v| params << ["metadata[#{k}]", v.to_s] }
|
|
391
|
+
elsif value.respond_to?(:utc)
|
|
392
|
+
params << [key.to_s, value.utc.iso8601]
|
|
393
|
+
else
|
|
394
|
+
params << [key.to_s, value.to_s]
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
params.map { |k, v| "#{ERB::Util.url_encode(k)}=#{ERB::Util.url_encode(v)}" }.join("&")
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def camelize(value)
|
|
401
|
+
value&.to_s&.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def each_in_pages(method, filters)
|
|
405
|
+
cursor = filters.delete(:cursor)
|
|
406
|
+
loop do
|
|
407
|
+
page = public_send(method, **filters, cursor: cursor)
|
|
408
|
+
page.items.each { |item| yield item }
|
|
409
|
+
cursor = page.next_cursor
|
|
410
|
+
break if cursor.nil? || cursor.empty?
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# A file id or label for a management path. Labels become PUBLIC_KEY%2BLABEL.
|
|
415
|
+
def file_ref(id_or_label)
|
|
416
|
+
value = id_or_label.to_s
|
|
417
|
+
raise InvalidArgumentError, "No id given" if value.empty?
|
|
418
|
+
|
|
419
|
+
# Build the raw reference first so it is percent-encoded exactly once
|
|
420
|
+
value = "#{@public_key}+#{value}" unless value.match?(UUID) || value.include?("+")
|
|
421
|
+
ERB::Util.url_encode(value)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def path_id(value, what = "id")
|
|
425
|
+
raise InvalidArgumentError, "No #{what} given" if value.to_s.empty?
|
|
426
|
+
|
|
427
|
+
ERB::Util.url_encode(value.to_s)
|
|
428
|
+
end
|
|
429
|
+
|
|
210
430
|
def stringify(metadata)
|
|
211
431
|
metadata.to_h.each_with_object({}) { |(k, v), memo| memo[k.to_s] = v.to_s }
|
|
212
432
|
end
|
|
@@ -243,7 +463,7 @@ module Fileboy
|
|
|
243
463
|
|
|
244
464
|
def request(method, path, json: nil, form: nil, raw: false, range: nil)
|
|
245
465
|
uri = URI.parse("#{@api_url}#{path}")
|
|
246
|
-
req = (method
|
|
466
|
+
req = HTTP_METHODS.fetch(method).new(uri)
|
|
247
467
|
req["Authorization"] = auth_header
|
|
248
468
|
req["Accept"] = "application/json" unless raw
|
|
249
469
|
req["User-Agent"] = "fileboy-ruby/#{VERSION}"
|
|
@@ -262,6 +482,7 @@ module Fileboy
|
|
|
262
482
|
|
|
263
483
|
raise_for_status(response) unless response.is_a?(Net::HTTPSuccess)
|
|
264
484
|
return response.body if raw
|
|
485
|
+
return nil if response.code.to_i == 204 || response.body.to_s.empty?
|
|
265
486
|
|
|
266
487
|
begin
|
|
267
488
|
JSON.parse(response.body)
|
data/lib/fileboy/file_data.rb
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
require "time"
|
|
2
2
|
|
|
3
3
|
module Fileboy
|
|
4
|
-
# The details of one stored file, as returned by an upload or
|
|
4
|
+
# The details of one stored file, as returned by an upload, `fetch_file_data` or the management calls.
|
|
5
|
+
# `labels` lists the labels pointing at the file and is only filled by the management calls.
|
|
5
6
|
FileData = Struct.new(
|
|
6
7
|
:id, :name, :original_filename, :mime, :metadata, :file_size, :content_hash, :signed,
|
|
7
|
-
:dies_at, :created_at,
|
|
8
|
+
:dies_at, :created_at, :labels,
|
|
8
9
|
keyword_init: true
|
|
9
10
|
) do
|
|
10
11
|
# Build from the `data` object of an API response.
|
|
@@ -19,7 +20,8 @@ module Fileboy
|
|
|
19
20
|
content_hash: data["hash"],
|
|
20
21
|
signed: data["signed"],
|
|
21
22
|
dies_at: parse_time(data["diesAt"]),
|
|
22
|
-
created_at: parse_time(data["createdAt"])
|
|
23
|
+
created_at: parse_time(data["createdAt"]),
|
|
24
|
+
labels: data["labels"] || []
|
|
23
25
|
)
|
|
24
26
|
end
|
|
25
27
|
|
|
@@ -34,4 +36,83 @@ module Fileboy
|
|
|
34
36
|
new(id: data["id"], name: data["name"], raw_file_id: data["rawFileId"])
|
|
35
37
|
end
|
|
36
38
|
end
|
|
39
|
+
|
|
40
|
+
# One page of a list. Enumerable over its items; `next_cursor` is nil on the last page.
|
|
41
|
+
class Page
|
|
42
|
+
include Enumerable
|
|
43
|
+
|
|
44
|
+
attr_reader :items, :next_cursor
|
|
45
|
+
|
|
46
|
+
def self.from_api(body, type)
|
|
47
|
+
new(items: body.fetch("data").map { |data| type.from_api(data) }, next_cursor: body["nextCursor"])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initialize(items:, next_cursor: nil)
|
|
51
|
+
@items = items
|
|
52
|
+
@next_cursor = next_cursor
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def each(&block)
|
|
56
|
+
return items.each unless block
|
|
57
|
+
|
|
58
|
+
items.each(&block)
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def last_page?
|
|
63
|
+
next_cursor.nil?
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# A video as the management API returns it. `status` is "pending", "processing", "ready" or
|
|
68
|
+
# "failed". `urls` is nil until ready, else a hash with :manifest, :hls, :dash and :poster.
|
|
69
|
+
ManagedVideo = Struct.new(
|
|
70
|
+
:id, :name, :created_at, :status, :status_message, :duration, :width, :height, :play_count,
|
|
71
|
+
:last_played_at, :urls,
|
|
72
|
+
keyword_init: true
|
|
73
|
+
) do
|
|
74
|
+
def self.from_api(data)
|
|
75
|
+
urls = data["urls"]
|
|
76
|
+
new(
|
|
77
|
+
id: data["id"], name: data["name"], created_at: FileData.parse_time(data["createdAt"]),
|
|
78
|
+
status: data["status"], status_message: data["statusMessage"], duration: data["duration"],
|
|
79
|
+
width: data["width"], height: data["height"], play_count: data["playCount"],
|
|
80
|
+
last_played_at: FileData.parse_time(data["lastPlayedAt"]),
|
|
81
|
+
urls: urls && urls.each_with_object({}) { |(k, v), memo| memo[k.to_sym] = v }
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# A label. `file_id` is the file it points at, or nil.
|
|
87
|
+
Label = Struct.new(:id, :name, :created_at, :file_id, keyword_init: true) do
|
|
88
|
+
def self.from_api(data)
|
|
89
|
+
new(id: data["id"], name: data["name"], created_at: FileData.parse_time(data["createdAt"]),
|
|
90
|
+
file_id: data["fileId"])
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# The result of `delete_files`. `failed` is a list of {FailedDelete}.
|
|
95
|
+
BulkDeleteResult = Struct.new(:deleted, :not_found, :failed, keyword_init: true) do
|
|
96
|
+
def self.from_api(data)
|
|
97
|
+
new(
|
|
98
|
+
deleted: data["deleted"] || [], not_found: data["notFound"] || [],
|
|
99
|
+
failed: (data["failed"] || []).map { |f| FailedDelete.new(id: f["id"], message: f["message"]) }
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
FailedDelete = Struct.new(:id, :message, keyword_init: true)
|
|
104
|
+
|
|
105
|
+
# Plan limits. A nil limit means none is set.
|
|
106
|
+
UsageLimits = Struct.new(:files, :videos, :storage_bytes, keyword_init: true)
|
|
107
|
+
|
|
108
|
+
# What the app stores. `storage_bytes` counts files, image copies, video renditions and posters.
|
|
109
|
+
Usage = Struct.new(:files, :videos, :storage_bytes, :limits, keyword_init: true) do
|
|
110
|
+
def self.from_api(data)
|
|
111
|
+
limits = data["limits"] || {}
|
|
112
|
+
new(
|
|
113
|
+
files: data["files"], videos: data["videos"], storage_bytes: data["storageBytes"],
|
|
114
|
+
limits: UsageLimits.new(files: limits["files"], videos: limits["videos"], storage_bytes: limits["storageBytes"])
|
|
115
|
+
)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
37
118
|
end
|
data/lib/fileboy/version.rb
CHANGED