isogit-lfs/src/download.ts

105 lines
2.8 KiB
TypeScript
Raw Permalink Normal View History

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;
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;
}
/**
* Downloads, caches and returns a blob corresponding to given LFS pointer.
* Uses already cached object, if size matches.
*/
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;
}
}
}
2023-05-16 22:46:18 +00:00
const authHeaders: Record<string, string> =
"username" in auth ? getAuthHeader(auth) : {};
// 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 doesnt seem to accept this UA, but works fine without any
// 'User-Agent': `git/isomorphic-git@${git.version()}`,
...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
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();
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);
}
}
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
}
}