fileboy 0.2.0 → 0.3.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 +28 -0
- data/README.md +179 -11
- data/lib/fileboy/client.rb +354 -19
- data/lib/fileboy/file_data.rb +193 -8
- data/lib/fileboy/signing.rb +198 -0
- data/lib/fileboy/transforms.rb +362 -15
- data/lib/fileboy/version.rb +1 -1
- data/lib/fileboy.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a84e114d8d513ec447db57039bd59c8535725bc0ebe278877574afcfa8eb8a1
|
|
4
|
+
data.tar.gz: 0f2c95d55b1ad22ffa35f620fc8daade39499d74919797c057d60ab7b30f713a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8be6951a4de0c4b97c777941873f933c10f774a67a1ae996b11ac3182917d723ec8867bdcac3e4757cbb1ac52c6b5550b395a302a6d67f5b2362fd8d582349a2
|
|
7
|
+
data.tar.gz: d9686d6a7d4758adbe1ec45ba94d6ee593e20cbfda476fbb7bf4d01bded65e95c476c2ce0c170c8253fa4b8173a1a80200c54f2f040d4a5f32000c46064cf680
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- New: `{ type: :format, format: :avif }` (and `:avif` as a `resize` format) asks for AVIF. `format: :auto` now serves AVIF to browsers that accept it, then WebP, with no change to the URL you build.
|
|
6
|
+
- New: overlay and text layers. `{ type: :overlay, file: logo_id, ... }` draws another image file of the same app on top (with `width`, `height` and its own `transforms`), and `{ type: :text, text: "...", ... }` writes text (`font`, `size`, `color`, `background`, `padding`, `wrap`, `align`). Both take `position`, `x`, `y`, `opacity`, `rotate`, and `tile` with `gap` for watermark patterns. Text is percent-encoded for you; `Fileboy::Transforms.encode_text` is the helper.
|
|
7
|
+
- New: a video's description, tags and metadata. `update_video` now takes `description:`, `tags:` (an Array replaces them, `{ add: [...], remove: [...] }` changes some) and `metadata:` (merged, nil removes a key) with `replace_metadata: true`, and `name:` is optional. `list_videos` filters by `tags:` (videos with every one) and `metadata:`, and `upload_video` takes all three.
|
|
8
|
+
- `Fileboy::ManagedVideo` and `Fileboy::Video` have `description`, `tags` and `metadata`.
|
|
9
|
+
- New: `fetch_file_exif(id_or_label)` returns a photo's EXIF, IPTC and XMP metadata as a `Fileboy::FileExif`: camera, lens, exposure, date taken (with its time zone when the file records one), orientation, GPS, colour profile and more as `Fileboy::ExifFields`, plus every raw tag. It needs the private key, because the GPS position and serial numbers are personal data; `personal: false` leaves those out.
|
|
10
|
+
- New: `fetch_file_palette(id_or_label, colors: 6)` returns an image's colour palette as a Hash: the dominant colour, up to 16 main colours with their share of the image, the named swatches (vibrant, muted and their dark and light versions) and a readable text colour for each. Needs the private key.
|
|
11
|
+
- New: `{ type: :palette }` builds the palette transform, which answers with the colours of the image as transformed so far: `output: :css` for CSS custom properties and classes, `colors:` (0 to 16) and, for CSS, `prefix:`.
|
|
12
|
+
- New: `fetch_storage_usage(days: 30, top: 10)` returns what your app stores by kind (images, other files, video originals, renditions, posters, captions and cached image copies), your biggest files and videos, and one total a day. `storage_bytes` is the same total `fetch_usage` reports. Needs the private key.
|
|
13
|
+
- New: folders. `create_folder(name, parent_id:)` or `create_folder(path:)` (creates every missing folder on the path), `list_folders` and `each_folder` (by parent, name or exact path), `fetch_folder` (with `ancestors`), `update_folder` (rename, or move with `parent_id:`, `nil` for the root) and `delete_folder` (`contents: :move_to_parent` or `:delete` for a folder that is not empty). They return a `Fileboy::Folder`.
|
|
14
|
+
- New: `move_files(ids, folder_id:)` moves up to 100 files at once, `nil` for the root, or `folder_path:`. `update_file` takes `folder_id:` and `folder_path:` too.
|
|
15
|
+
- `list_files` filters by `folder_id:` (or `"root"`), `folder_path:` and `recursive:`. `upload_file` and `upload_file_from_url` take `folder_id:` or `folder_path:`.
|
|
16
|
+
- `Fileboy::FileData` has `folder_id` and `folder_path`.
|
|
17
|
+
- New: expiring links you can revoke. `create_file_link(id, expires_in: 7 * 24 * 3600)` makes a URL to one file (optionally an image transform, as a download, under a filename) that works until it expires or you call `revoke_file_link(id, link_id)`, even when the file or app needs signed reads. `list_file_links` and `each_file_link` list a file's links with their use counts. Needs the private key; results are `Fileboy::FileLink` structs.
|
|
18
|
+
- `link_not_found` raises `NotFoundError`; `too_many_links` (100 active links on a file) and `invalid_transform` raise `BadRequestError`.
|
|
19
|
+
- New: share pages. `update_file(id, share_page: true)` turns a file's share page on, `build_share_page_url(id)` gives its link and `sign_share_page_url(id, expires_in: ...)` a signed link for a file that needs a signed URL. `FileData#share_page` says whether it is on, and `Client.new(website_url: ...)` builds links on another host. `sign_url` also signs share page URLs (`/f/{id}`, scope `share`).
|
|
20
|
+
|
|
21
|
+
- New: `build_video_thumbnails_url(video_id)` is the URL of a video's seek-preview thumbnails, a WebVTT file with a sprite sheet that the Fileboy player, Plyr, Video.js and most other players read. `format: :json` gives the same as a JSON storyboard.
|
|
22
|
+
- New: signed URLs for apps that require signed reads. `client.sign_url(url, expires_in: 600)` signs the URL of a file, image or video (including every transform URL) with your private key, `client.sign_video(video_id)` returns the query string that unlocks every URL of one video, and `Fileboy::Signing` has the lower level pieces. Signing needs the private key and happens on your server.
|
|
23
|
+
- New: `fetch_require_signed_reads` and `update_require_signed_reads(true)` read and change the app's setting from code.
|
|
24
|
+
- Signatures are identical across the Node, Ruby, Python and PHP SDKs, they test against the same fixtures (`packages/sdk-fixtures/signing.json`).
|
|
25
|
+
- New: every image transform the service supports can be built. Added `resizeAR`, `crop`, `smartCrop`, `rotate`, `flip`, `blur`, `sharpen`, `saturate`, `grayscale`, `brightness`, `contrast`, `hue`, `gamma`, `sepia`, `tint`, `opacity`, `pixelate`, `radius`, `pad`, `border`, `dpr` and `strip`, `format: :auto` and a `focus` point for `resize`.
|
|
26
|
+
- Colours can now be `rgb(...)`, `rgba(...)` or a CSS colour name as well as hex.
|
|
27
|
+
- New: `upscale: false` on `resize` never makes an image bigger than it is (`~upscale:false`), `trim` cuts off a plain border (optional `threshold` and `color`), `lossless` asks for lossless WebP, and `{ type: :format, format: :json }` returns the image's details as JSON. `json` is not accepted as a `resize` format. In the hash form `{ trim: true }`, `{ trim: 20 }` and `{ lossless: true }` work too.
|
|
28
|
+
- The hash form skips `false` values, so `{ grayscale: flag }` only adds the transform when `flag` is true (it used to send `grayscale:false`).
|
|
29
|
+
- The `transform` value in image URLs is percent-encoded where needed (`+`, `&`, `#`, `%`, spaces), so a number like `1e+21` reaches the image service intact. Ordinary transforms give the same URL as before.
|
|
30
|
+
|
|
3
31
|
## 0.2.0
|
|
4
32
|
|
|
5
33
|
- 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.
|
data/README.md
CHANGED
|
@@ -44,15 +44,16 @@ end
|
|
|
44
44
|
|
|
45
45
|
| Task | Method |
|
|
46
46
|
| --- | --- |
|
|
47
|
-
| Upload a path or IO | `upload_file(file, name:, mime:, metadata:)` |
|
|
48
|
-
| Upload from a URL | `upload_file_from_url(url, name:, metadata:, expect:)` |
|
|
47
|
+
| Upload a path or IO | `upload_file(file, name:, mime:, metadata:, folder_id:, folder_path:)` |
|
|
48
|
+
| Upload from a URL | `upload_file_from_url(url, name:, metadata:, expect:, folder_id:, folder_path:)` |
|
|
49
49
|
| File details | `fetch_file_data(id_or_label)` |
|
|
50
50
|
| File URL | `build_file_url(id_or_label, filename:, download:)` |
|
|
51
51
|
| Image URL with transforms | `build_image_url(id_or_label, transforms:, filename:, download:)` |
|
|
52
52
|
| Download bytes | `download_file`, `download_image` |
|
|
53
|
-
| Upload a video | `upload_video(file, name:)` |
|
|
53
|
+
| Upload a video | `upload_video(file, name:, description:, tags:, metadata:)` |
|
|
54
54
|
| Video details | `fetch_video_data(id)`, `fetch_video_info(id)` |
|
|
55
55
|
| Manifest and poster URLs | `build_video_manifest_url(id, type: :hls)`, `build_video_poster_url(id)` |
|
|
56
|
+
| Seek-preview thumbnails | `build_video_thumbnails_url(id)` (WebVTT), `build_video_thumbnails_url(id, format: :json)` |
|
|
56
57
|
| Custom poster | `upload_video_poster(video_id, image)` |
|
|
57
58
|
| Transcripts | `upload_video_transcript(id, file, language: "English")`, `fetch_video_transcript(id, transcript_id)` |
|
|
58
59
|
| Signed access token | `create_access_token(read:, write:, upload:, expires_in:)` |
|
|
@@ -62,21 +63,70 @@ created in the dashboard.
|
|
|
62
63
|
|
|
63
64
|
### Image transforms
|
|
64
65
|
|
|
65
|
-
`transforms:` takes an array of hashes applied in order.
|
|
66
|
-
`
|
|
66
|
+
`transforms:` takes an array of hashes applied in order. Pixel sizes are whole numbers; where a
|
|
67
|
+
percentage is allowed write `{ pc: 50 }` (or `"50pc"`). Colours can be `#rgb`, `#rrggbb`,
|
|
68
|
+
`#rrggbbaa`, `rgb(...)`, `rgba(...)` or a CSS name like `"white"`.
|
|
67
69
|
|
|
68
70
|
```ruby
|
|
69
71
|
[
|
|
72
|
+
# Size and shape
|
|
70
73
|
{ type: :resize, width: 200, height: 200, fit: :cover }, # fit: contain cover fill inside outside
|
|
71
74
|
{ type: :resize, width: 400, fit: :contain, position: "top-left", background: "#ffffff" },
|
|
72
|
-
{ type: :
|
|
75
|
+
{ type: :resize, width: 300, height: 300, focus: { x: { pc: 30 }, y: { pc: 70 } } }, # keep this point in view
|
|
76
|
+
{ type: :resize, width: 800, upscale: false }, # never bigger than the original
|
|
77
|
+
{ type: :resizeAR, ratio: [16, 9], fit: :contain, background: "black" },
|
|
78
|
+
{ type: :crop, width: 400, height: 300, left: { pc: 50 }, top: 120 }, # centre of the area
|
|
79
|
+
{ type: :crop, width: { pc: 50 }, height: { pc: 50 }, position: "top-left" },
|
|
80
|
+
{ type: :smartCrop, width: 400, height: 400 }, # crops to the most detailed area
|
|
81
|
+
{ type: :trim, threshold: 10, color: "white" }, # cuts off a plain border; both optional
|
|
82
|
+
{ type: :rotate, angle: 90 }, # clockwise; other angles leave a background
|
|
83
|
+
{ type: :flip, direction: :x }, # x y xy
|
|
84
|
+
{ type: :radius, radius: 24 }, # or "max" for a circle
|
|
85
|
+
{ type: :pad, padding: [10, 20], background: "#fff" }, # all, [v, h] or [top, right, bottom, left]
|
|
86
|
+
{ type: :border, width: 4, color: "#000" },
|
|
87
|
+
{ type: :dpr, ratio: 2 }, # multiplies every pixel size
|
|
88
|
+
# Look
|
|
89
|
+
{ type: :blur, amount: 5 }, # 0 to 100
|
|
90
|
+
{ type: :sharpen }, # or amount: 0 to 10
|
|
91
|
+
{ type: :grayscale },
|
|
92
|
+
{ type: :saturate, amount: 1.5 }, # 0 grey, 1 unchanged
|
|
93
|
+
{ type: :brightness, amount: 10 }, # -100 to 100
|
|
94
|
+
{ type: :contrast, amount: 10 }, # -100 to 100
|
|
95
|
+
{ type: :hue, degrees: 90 },
|
|
96
|
+
{ type: :gamma, value: 1.2 },
|
|
97
|
+
{ type: :sepia }, # or amount: 0 to 100
|
|
98
|
+
{ type: :tint, color: "#ff0000" },
|
|
99
|
+
{ type: :opacity, amount: 50 },
|
|
100
|
+
{ type: :pixelate, size: 12 },
|
|
101
|
+
{ type: :invert },
|
|
102
|
+
# Layers: another image file of the same app, or text, drawn on top
|
|
103
|
+
{ type: :overlay, file: logo_id, position: "bottom-right", x: 20, y: 20, width: { pc: 20 }, opacity: 80 },
|
|
104
|
+
{ type: :overlay, file: logo_id, tile: true, gap: 40, rotate: -30, opacity: 25 }, # a watermark pattern
|
|
105
|
+
{ type: :overlay, file: logo_id, transforms: [{ type: :grayscale }, { type: :radius, radius: "max" }] },
|
|
106
|
+
{ type: :text, text: "© 2026 Fileboy", font: "sans-bold", size: { pc: 4 }, color: "#fff",
|
|
107
|
+
background: "#00000080", padding: 8, position: "bottom-left", x: 16, y: 16 },
|
|
108
|
+
# Output
|
|
109
|
+
{ type: :format, format: :auto }, # auto (AVIF, then WebP, when the browser takes them) avif webp jpeg png tiff bmp
|
|
110
|
+
{ type: :format, format: :json }, # the image's details as JSON, not an image
|
|
111
|
+
{ type: :palette, output: :css, colors: 4 }, # the main colours as JSON (default) or CSS, not an image
|
|
73
112
|
{ type: :quality, value: 75 },
|
|
74
|
-
{ type: :
|
|
75
|
-
{ type: :
|
|
113
|
+
{ type: :lossless }, # lossless WebP or AVIF
|
|
114
|
+
{ type: :background, color: "#fff" }, # default for transforms that leave space
|
|
115
|
+
{ type: :strip } # re-encode without metadata
|
|
76
116
|
]
|
|
77
117
|
```
|
|
78
118
|
|
|
79
|
-
|
|
119
|
+
Overlays and text take `position` (default `center`), `x` and `y` (moved in from that edge, pixels
|
|
120
|
+
or `{ pc: n }`, negative allowed), `opacity` (0 to 100), `rotate`, and `tile` with `gap`. An overlay
|
|
121
|
+
is the id of an image file in the same app; `width` and `height` size it (one keeps its shape, both
|
|
122
|
+
fit it inside the box) and `transforms` run on it first. Text is encoded for you
|
|
123
|
+
(`Fileboy::Transforms.encode_text`), in the fonts `sans`, `sans-bold`, `sans-italic`, `mono` or
|
|
124
|
+
`mono-bold`, with `size` (pixels, or `{ pc: n }` of the image's height), `color`, `background`,
|
|
125
|
+
`padding`, `wrap` and `align`. If the overlay file is set to signed URLs only, sign the image URL:
|
|
126
|
+
the signature covers the overlay.
|
|
127
|
+
|
|
128
|
+
You can also pass a hash of `name => value`, such as `{ resize: "300x_", format: "webp", grayscale: true }`, or a ready-made string.
|
|
129
|
+
The builder checks what it can before you make a request; the image service answers anything it cannot apply with a 400 that says why.
|
|
80
130
|
|
|
81
131
|
## Managing files
|
|
82
132
|
|
|
@@ -103,16 +153,38 @@ fileboy.update_file("hero", name: "x") # a label name works too
|
|
|
103
153
|
fileboy.delete_file(file.id) # raises Fileboy::NotFoundError if it is gone
|
|
104
154
|
result = fileboy.delete_files([id1, id2]) # up to 100; result.deleted, .not_found, .failed
|
|
105
155
|
|
|
106
|
-
fileboy.list_videos(status: :ready).items
|
|
156
|
+
fileboy.list_videos(status: :ready, tags: ["launch"], metadata: { orderId: "42" }).items
|
|
107
157
|
fileboy.update_video(video_id, name: "Launch.mp4")
|
|
158
|
+
# The description shows on the watch page. Tags and metadata are private, for your own records.
|
|
159
|
+
video = fileboy.update_video(video_id, description: "Launch day", tags: { add: ["launch"], remove: ["draft"] },
|
|
160
|
+
metadata: { orderId: "42", old: nil })
|
|
161
|
+
video.description, video.tags, video.metadata # also in every video.* webhook
|
|
108
162
|
fileboy.delete_video(video_id)
|
|
109
163
|
|
|
110
164
|
fileboy.list_labels # [Fileboy::Label]
|
|
111
165
|
fileboy.create_label("hero", file_id: file.id) # BadRequestError with code "label_taken" if it exists
|
|
112
166
|
fileboy.delete_label("hero") # the file is kept
|
|
113
167
|
|
|
168
|
+
# An image's colours: the dominant one, up to 16 main colours with their share of the image,
|
|
169
|
+
# vibrant and muted swatches, and a readable text colour (black or white) for each
|
|
170
|
+
palette = fileboy.fetch_file_palette(file.id, colors: 4)
|
|
171
|
+
palette["dominant"]["hex"]; palette["colors"].first["share"]; palette["swatches"]["vibrant"]
|
|
172
|
+
|
|
114
173
|
usage = fileboy.fetch_usage
|
|
115
|
-
usage.files; usage.storage_bytes; usage.limits.storage_bytes #
|
|
174
|
+
usage.files; usage.storage_bytes; usage.limits.storage_bytes # nil: no limit set on the app (not unlimited)
|
|
175
|
+
|
|
176
|
+
# A photo's camera, lens, exposure, date taken and GPS position (EXIF, IPTC and XMP)
|
|
177
|
+
exif = fileboy.fetch_file_exif(file.id)
|
|
178
|
+
exif.fields.model; exif.fields.aperture; exif.fields.shutter_speed; exif.fields.date_taken
|
|
179
|
+
exif.fields.gps # Fileboy::ExifGps (latitude, longitude, altitude) or nil
|
|
180
|
+
exif.raw["exif"]["LensModel"] # every tag read, by source
|
|
181
|
+
fileboy.fetch_file_exif(file.id, personal: false) # without GPS, place names, serial numbers or owner
|
|
182
|
+
|
|
183
|
+
# What the storage is made of, the biggest files and videos, and a daily total
|
|
184
|
+
storage = fileboy.fetch_storage_usage(days: 90, top: 5)
|
|
185
|
+
storage.kinds["videoRenditions"].bytes # also images, files, videoOriginals, imageTransforms...
|
|
186
|
+
storage.largest_files.first.id; storage.largest_videos.first.storage_bytes
|
|
187
|
+
storage.history.map { |day| [day.date, day.storage_bytes] }
|
|
116
188
|
```
|
|
117
189
|
|
|
118
190
|
Failures carry the API's `code`, message and HTTP `status`:
|
|
@@ -126,6 +198,102 @@ rescue Fileboy::NotFoundError => e
|
|
|
126
198
|
end
|
|
127
199
|
```
|
|
128
200
|
|
|
201
|
+
### Expiring links
|
|
202
|
+
|
|
203
|
+
An expiring link is a URL to one file that works until it expires or you revoke it. Hand it to a
|
|
204
|
+
customer, an auditor or a partner, and take it back if you need to. Needs the private key.
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
# A link that works for 7 days
|
|
208
|
+
link = fileboy.create_file_link(file.id, expires_in: 7 * 24 * 3600, note: "for the auditors")
|
|
209
|
+
link.url # https://api.fileboy.io/links/...
|
|
210
|
+
link.expires_at # a Time
|
|
211
|
+
|
|
212
|
+
# A resized WebP of an image, downloaded as logo.webp, until a set time
|
|
213
|
+
fileboy.create_file_link("logo", expires_at: Time.utc(2026, 12, 1, 9), transform: "resize:400x_;format:webp",
|
|
214
|
+
download: true, filename: "logo.webp")
|
|
215
|
+
|
|
216
|
+
page = fileboy.list_file_links(file.id, status: :active) # newest first; status: :all is the default
|
|
217
|
+
page.each { |l| puts "#{l.id} #{l.status} #{l.use_count} #{l.last_used_at}" }
|
|
218
|
+
fileboy.each_file_link(file.id).to_a # every page
|
|
219
|
+
|
|
220
|
+
fileboy.revoke_file_link(file.id, link.id) # stops working at once, safe to repeat
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Give exactly one of `expires_in` (seconds) or `expires_at` (a `Time`, `DateTime` or ISO 8601
|
|
224
|
+
string), from one minute to 365 days away. A file can have at most 100 active links
|
|
225
|
+
(`BadRequestError` with code `too_many_links`). Results are `Fileboy::FileLink` structs (`id`,
|
|
226
|
+
`file_id`, `url`, `status`, `expires_at`, `revoked_at`, `transform`, `download`, `filename`, `note`,
|
|
227
|
+
`created_via`, `created_at`, `last_used_at`, `use_count`).
|
|
228
|
+
|
|
229
|
+
A link works until it expires or is revoked even if the file is "Signed URLs only" or the app
|
|
230
|
+
requires signed reads. It does not make a public file private: the file's own URL keeps working.
|
|
231
|
+
For a link per page view, `sign_url` is the better tool (no request, nothing stored); stored links
|
|
232
|
+
are for links you hand out and might take back.
|
|
233
|
+
|
|
234
|
+
## Folders
|
|
235
|
+
|
|
236
|
+
Folders organise files. They are never part of a URL, so moving or renaming one changes no links.
|
|
237
|
+
Every file starts at the root. These calls need the private key, like the rest of the management calls.
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
shoes = fileboy.create_folder(path: "/Products/Shoes") # creates what is missing, like mkdir -p
|
|
241
|
+
sale = fileboy.create_folder("Sale", parent_id: shoes.id) # BadRequestError "folder_exists" if it is taken
|
|
242
|
+
|
|
243
|
+
file = fileboy.upload_file("shoe.png", folder_path: "/Products/Shoes") # or folder_id: shoes.id
|
|
244
|
+
file.folder_id; file.folder_path
|
|
245
|
+
|
|
246
|
+
fileboy.list_files(folder_id: shoes.id) # only the files directly in it
|
|
247
|
+
fileboy.list_files(folder_path: "/Products", recursive: true) # subfolders included
|
|
248
|
+
fileboy.list_files(folder_id: "root") # files in no folder
|
|
249
|
+
|
|
250
|
+
fileboy.update_file(file.id, folder_path: "/Archive") # one file; folder_id: nil moves it to the root
|
|
251
|
+
result = fileboy.move_files([id1, id2], folder_id: shoes.id) # up to 100; result.moved, .not_found, .folder
|
|
252
|
+
fileboy.move_files([id1], folder_id: nil) # to the root
|
|
253
|
+
|
|
254
|
+
fileboy.list_folders(parent_id: "root").items # top level, by name
|
|
255
|
+
fileboy.list_folders(path: "/Products/Shoes").items # [] or the one folder
|
|
256
|
+
fileboy.fetch_folder(shoes.id).ancestors # the folders above, for breadcrumbs
|
|
257
|
+
|
|
258
|
+
fileboy.update_folder(shoes.id, name: "Boots")
|
|
259
|
+
fileboy.update_folder(shoes.id, parent_id: nil) # move it to the root
|
|
260
|
+
|
|
261
|
+
fileboy.delete_folder(sale.id) # only an empty folder, else "folder_not_empty"
|
|
262
|
+
fileboy.delete_folder(shoes.id, contents: :move_to_parent) # its files and folders move up a level
|
|
263
|
+
fileboy.delete_folder(shoes.id, contents: :delete) # deletes the files too, at most 1000
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Uploads made with only the public key, or an access token, can use folders that exist but cannot
|
|
267
|
+
create them.
|
|
268
|
+
|
|
269
|
+
## Signed URLs
|
|
270
|
+
|
|
271
|
+
If your app requires signed reads, sign URLs on your server with the private key:
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
url = Fileboy.client.sign_url(Fileboy.client.build_image_url(id, transforms: { resize: [400, 400] }), expires_in: 600)
|
|
275
|
+
query = Fileboy.client.sign_video(video_id, expires_in: 4 * 3600) # add to any /videos/{id}/... URL
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Signing makes no request. See https://fileboy.io/docs/delivery/signed-urls.
|
|
279
|
+
|
|
280
|
+
## Share pages
|
|
281
|
+
|
|
282
|
+
A share page is a page anyone can open at `https://fileboy.io/f/{id}`, with a preview of the file
|
|
283
|
+
and a download button. It is off until you turn it on, and it follows the file's read access:
|
|
284
|
+
|
|
285
|
+
```ruby
|
|
286
|
+
Fileboy.client.update_file(id, share_page: true)
|
|
287
|
+
link = Fileboy.client.build_share_page_url(id) # https://fileboy.io/f/2a86...
|
|
288
|
+
|
|
289
|
+
# A file set to Signed URLs only, or any file of an app that requires signed reads, only opens
|
|
290
|
+
# through a signed share link. Its preview and download stop working when the link does.
|
|
291
|
+
signed_link = Fileboy.client.sign_share_page_url(id, expires_in: 7 * 24 * 3600)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`update_file(id, share_page: false)` turns the page off, and every link to it stops working.
|
|
295
|
+
`FileData#share_page` says whether it is on. See https://fileboy.io/docs/delivery/share-pages.
|
|
296
|
+
|
|
129
297
|
## Errors
|
|
130
298
|
|
|
131
299
|
Every error is a `Fileboy::Error` with `status`, `code` (the API's error code) and `body`.
|