1 | import * as wmill from "windmill-client" |
2 | import { S3Object } from "windmill-client" |
3 |
|
4 | const graph = "https://graph.microsoft.com/v1.0" |
5 |
|
6 | function driveUrl(drive_id: string | undefined) { |
7 | return drive_id != null && drive_id !== "" |
8 | ? `${graph}/drives/${encodeURIComponent(drive_id)}` |
9 | : `${graph}/me/drive` |
10 | } |
11 |
|
12 | |
13 | function ref(item: string | undefined) { |
14 | if (item == null || item === "" || item === "/") return "root" |
15 | if (!item.startsWith("/")) return `items/${encodeURIComponent(item)}` |
16 | const path = item |
17 | .replace(/\/+$/, "") |
18 | .split("/") |
19 | .map(encodeURIComponent) |
20 | .join("/") |
21 | return `root:${path}:` |
22 | } |
23 |
|
24 | |
25 | * Upload File |
26 | * Upload a file from Windmill object storage to a folder in your OneDrive, or in another drive when `drive_id` is set. `folder` is an id, or a path from the drive root starting with "/" (e.g. `/Reports/2026`), the root when empty; `name` defaults to the object's file name. Files over 4 MB go through a resumable upload session in 10 MB chunks. Requires Files.ReadWrite (a connection default); a drive that is not your own needs Files.ReadWrite.All. |
27 | */ |
28 | export async function main( |
29 | auth: RT.MsOnedrive, |
30 |
|
31 | file: S3Object, |
32 | folder: string | undefined, |
33 | name: string | undefined, |
34 | conflict_behavior: "rename" | "replace" | "fail" = "rename", |
35 | drive_id: string | undefined |
36 | ) { |
37 | const drive = driveUrl(drive_id) |
38 | |
39 | const bytes = await wmill.loadS3File(file) |
40 | if (!bytes) throw new Error(`Could not load ${file.s3}`) |
41 |
|
42 | const fileName = encodeURIComponent( |
43 | name != null && name !== "" ? name : file.s3.split("/").pop()! |
44 | ) |
45 | const parent = ref(folder) |
46 | const target = parent.startsWith("items/") |
47 | ? `${drive}/${parent}:/${fileName}:` |
48 | : `${drive}/root:${parent === "root" ? "" : parent.slice(5, -1)}/${fileName}:` |
49 |
|
50 | if (bytes.length <= 4 * 1024 * 1024) { |
51 | const response = await fetch( |
52 | `${target}/[email protected]=${conflict_behavior}`, |
53 | { |
54 | method: "PUT", |
55 | headers: { |
56 | Authorization: `Bearer ${auth.token}`, |
57 | "Content-Type": "application/octet-stream", |
58 | Accept: "application/json", |
59 | }, |
60 | body: bytes, |
61 | } |
62 | ) |
63 | if (!response.ok) { |
64 | throw new Error(`${response.status} ${await response.text()}`) |
65 | } |
66 | return await response.json() |
67 | } |
68 |
|
69 | const session = await fetch(`${target}/createUploadSession`, { |
70 | method: "POST", |
71 | headers: { |
72 | Authorization: `Bearer ${auth.token}`, |
73 | "Content-Type": "application/json", |
74 | Accept: "application/json", |
75 | }, |
76 | body: JSON.stringify({ |
77 | item: { "@microsoft.graph.conflictBehavior": conflict_behavior }, |
78 | }), |
79 | }) |
80 | if (!session.ok) { |
81 | throw new Error(`${session.status} ${await session.text()}`) |
82 | } |
83 | const { uploadUrl } = (await session.json()) as { uploadUrl: string } |
84 |
|
85 | |
86 | const chunk = 10 * 1024 * 1024 |
87 | for (let start = 0; ; start += chunk) { |
88 | const end = Math.min(start + chunk, bytes.length) |
89 | const response = await fetch(uploadUrl, { |
90 | method: "PUT", |
91 | headers: { |
92 | "Content-Range": `bytes ${start}-${end - 1}/${bytes.length}`, |
93 | }, |
94 | body: bytes.subarray(start, end), |
95 | }) |
96 | if (!response.ok) { |
97 | throw new Error(`${response.status} ${await response.text()}`) |
98 | } |
99 | if (end === bytes.length) return await response.json() |
100 | } |
101 | } |
102 |
|