2022-12-07 23:39:09 +00:00
|
|
|
|
import path from "path-browserify";
|
|
|
|
|
import { Buffer } from "buffer";
|
2021-11-25 12:27:33 +00:00
|
|
|
|
|
2022-12-07 23:39:09 +00:00
|
|
|
|
import { bodyToBuffer, getAuthHeader, isWriteable } from "./util";
|
|
|
|
|
import { Pointer } from "./pointers";
|
2023-05-16 22:46:18 +00:00
|
|
|
|
import { HTTPRequest, BasicAuth } from "./types";
|
2022-12-07 23:39:09 +00:00
|
|
|
|
import { PromiseFsClient } from "isomorphic-git";
|
2021-11-25 12:27:33 +00:00
|
|
|
|
|
|
|
|
|
interface LFSInfoResponse {
|
|
|
|
|
objects: {
|
|
|
|
|
actions: {
|
|
|
|
|
download: {
|
|
|
|
|
href: string;
|
2021-11-29 01:56:09 +00:00
|
|
|
|
header?: Record<string, string>;
|
2021-11-25 12:27:33 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}[];
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-07 23:39:09 +00:00
|
|
|
|
function isValidLFSInfoResponseData(
|
|
|
|
|
val: Record<string, any>
|
|
|
|
|
): val is LFSInfoResponse {
|
2021-11-25 12:27:33 +00:00
|
|
|
|
return val.objects?.[0]?.actions?.download?.href?.trim !== undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-29 01:56:58 +00:00
|
|
|
|
/**
|
|
|
|
|
* Downloads, caches and returns a blob corresponding to given LFS pointer.
|
2021-12-02 13:15:13 +00:00
|
|
|
|
* Uses already cached object, if size matches.
|
2021-11-29 01:56:58 +00:00
|
|
|
|
*/
|
2021-11-25 12:27:33 +00:00
|
|
|
|
export default async function downloadBlobFromPointer(
|
2023-05-16 22:39:48 +00:00
|
|
|
|
fs: PromiseFsClient | null,
|
2023-05-16 22:46:18 +00:00
|
|
|
|
url: string,
|
|
|
|
|
auth: BasicAuth | {},
|
2022-12-07 23:39:09 +00:00
|
|
|
|
{ info, objectPath }: Pointer
|
2023-05-16 22:46:18 +00:00
|
|
|
|
): Promise<Blob> {
|
2023-05-16 22:39:48 +00:00
|
|
|
|
if (fs) {
|
|
|
|
|
try {
|
|
|
|
|
const cached = await fs?.promises.readFile(objectPath);
|
|
|
|
|
if (cached.byteLength === info.size) {
|
|
|
|
|
return cached;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// Silence file not found errors (implies cache miss)
|
|
|
|
|
if ((e as any).code !== "ENOENT") {
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
2021-12-02 13:15:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-16 22:46:18 +00:00
|
|
|
|
const authHeaders: Record<string, string> =
|
|
|
|
|
"username" in auth ? getAuthHeader(auth) : {};
|
2021-11-29 13:27:07 +00:00
|
|
|
|
|
2021-11-30 21:18:27 +00:00
|
|
|
|
// Request LFS transfer
|
2021-11-25 12:27:33 +00:00
|
|
|
|
|
|
|
|
|
const lfsInfoRequestData = {
|
2022-12-07 23:39:09 +00:00
|
|
|
|
operation: "download",
|
|
|
|
|
transfers: ["basic"],
|
2021-11-25 12:27:33 +00:00
|
|
|
|
objects: [info],
|
|
|
|
|
};
|
|
|
|
|
|
2023-05-16 22:46:18 +00:00
|
|
|
|
const lfsInfoRes = await fetch(`${url}/info/lfs/objects/batch`, {
|
2022-12-07 23:39:09 +00:00
|
|
|
|
method: "POST",
|
2021-11-25 12:27:33 +00:00
|
|
|
|
headers: {
|
2021-12-02 18:06:40 +00:00
|
|
|
|
// Github LFS doesn’t seem to accept this UA, but works fine without any
|
2021-11-29 01:55:23 +00:00
|
|
|
|
// 'User-Agent': `git/isomorphic-git@${git.version()}`,
|
2021-11-29 13:27:07 +00:00
|
|
|
|
...authHeaders,
|
2022-12-07 23:39:09 +00:00
|
|
|
|
Accept: "application/vnd.git-lfs+json",
|
|
|
|
|
"Content-Type": "application/vnd.git-lfs+json",
|
2021-11-25 12:27:33 +00:00
|
|
|
|
},
|
2023-05-16 22:46:18 +00:00
|
|
|
|
body: JSON.stringify(lfsInfoRequestData),
|
2021-11-25 12:27:33 +00:00
|
|
|
|
});
|
2023-05-16 22:46:18 +00:00
|
|
|
|
const lfsInfoResponseData = await lfsInfoRes.json();
|
2021-11-25 12:27:33 +00:00
|
|
|
|
|
|
|
|
|
if (isValidLFSInfoResponseData(lfsInfoResponseData)) {
|
|
|
|
|
// Request the actual blob
|
2021-11-25 12:36:13 +00:00
|
|
|
|
|
2021-11-29 01:56:09 +00:00
|
|
|
|
const downloadAction = lfsInfoResponseData.objects[0].actions.download;
|
|
|
|
|
const lfsObjectDownloadURL = downloadAction.href;
|
|
|
|
|
const lfsObjectDownloadHeaders = downloadAction.header ?? {};
|
|
|
|
|
|
2023-05-16 22:46:18 +00:00
|
|
|
|
const lfsObjectRes = await fetch(lfsObjectDownloadURL, {
|
2022-12-07 23:39:09 +00:00
|
|
|
|
method: "GET",
|
2023-05-16 22:46:18 +00:00
|
|
|
|
headers: {
|
|
|
|
|
...authHeaders,
|
|
|
|
|
...lfsObjectDownloadHeaders,
|
|
|
|
|
},
|
2021-11-25 12:27:33 +00:00
|
|
|
|
});
|
2023-05-16 22:46:18 +00:00
|
|
|
|
const blob = await lfsObjectRes.blob();
|
2021-11-29 01:56:58 +00:00
|
|
|
|
|
2023-05-16 22:39:48 +00:00
|
|
|
|
if (fs) {
|
|
|
|
|
// Write LFS cache for this object, if cache path is accessible.
|
|
|
|
|
if (await isWriteable(fs, objectPath)) {
|
|
|
|
|
await fs.promises.mkdir(path.dirname(objectPath), { recursive: true });
|
|
|
|
|
await fs.promises.writeFile(objectPath, blob);
|
|
|
|
|
}
|
2021-11-29 01:56:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return blob;
|
2021-11-25 12:27:33 +00:00
|
|
|
|
} else {
|
2022-12-07 23:39:09 +00:00
|
|
|
|
throw new Error(
|
|
|
|
|
"Unexpected JSON structure received for LFS download request"
|
|
|
|
|
);
|
2021-11-25 12:27:33 +00:00
|
|
|
|
}
|
|
|
|
|
}
|