37138834d6
* fix thumbnail quality * Revert "fix(git-gateway): fix previews for GitHub images not in Large Media (#2125)" This reverts commit d17f896f479292db06d3a4b39f2e51b6c41101bd. * wip * Stop using thunks to load media display URLs * Revert changes to dev-test * Revert changes to large media docs * fix lint error * Update docs to point to the upcoming version with non-broken media
184 lines
5.2 KiB
JavaScript
184 lines
5.2 KiB
JavaScript
import { filter, flow, fromPairs, map } from 'lodash/fp';
|
|
import minimatch from 'minimatch';
|
|
|
|
//
|
|
// Pointer file parsing
|
|
|
|
const splitIntoLines = str => str.split('\n');
|
|
const splitIntoWords = str => str.split(/\s+/g);
|
|
const isNonEmptyString = str => str !== '';
|
|
const withoutEmptyLines = flow([map(str => str.trim()), filter(isNonEmptyString)]);
|
|
export const parsePointerFile = flow([
|
|
splitIntoLines,
|
|
withoutEmptyLines,
|
|
map(splitIntoWords),
|
|
fromPairs,
|
|
({ size, oid, ...rest }) => ({
|
|
size: parseInt(size),
|
|
sha: oid.split(':')[1],
|
|
...rest,
|
|
}),
|
|
]);
|
|
|
|
export const createPointerFile = ({ size, sha }) => `\
|
|
version https://git-lfs.github.com/spec/v1
|
|
oid sha256:${sha}
|
|
size ${size}
|
|
`;
|
|
|
|
//
|
|
// .gitattributes file parsing
|
|
|
|
const removeGitAttributesCommentsFromLine = line => line.split('#')[0];
|
|
|
|
const parseGitPatternAttribute = attributeString => {
|
|
// There are three kinds of attribute settings:
|
|
// - a key=val pair sets an attribute to a specific value
|
|
// - a key without a value and a leading hyphen sets an attribute to false
|
|
// - a key without a value and no leading hyphen sets an attribute
|
|
// to true
|
|
if (attributeString.includes('=')) {
|
|
return attributeString.split('=');
|
|
}
|
|
if (attributeString.startsWith('-')) {
|
|
return [attributeString.slice(1), false];
|
|
}
|
|
return [attributeString, true];
|
|
};
|
|
|
|
const parseGitPatternAttributes = flow([map(parseGitPatternAttribute), fromPairs]);
|
|
|
|
const parseGitAttributesPatternLine = flow([
|
|
splitIntoWords,
|
|
([pattern, ...attributes]) => [pattern, parseGitPatternAttributes(attributes)],
|
|
]);
|
|
|
|
const parseGitAttributesFileToPatternAttributePairs = flow([
|
|
splitIntoLines,
|
|
map(removeGitAttributesCommentsFromLine),
|
|
withoutEmptyLines,
|
|
map(parseGitAttributesPatternLine),
|
|
]);
|
|
|
|
export const getLargeMediaPatternsFromGitAttributesFile = flow([
|
|
parseGitAttributesFileToPatternAttributePairs,
|
|
filter(
|
|
// eslint-disable-next-line no-unused-vars
|
|
([pattern, attributes]) =>
|
|
attributes.filter === 'lfs' && attributes.diff === 'lfs' && attributes.merge === 'lfs',
|
|
),
|
|
map(([pattern]) => pattern),
|
|
]);
|
|
|
|
export const matchPath = ({ patterns }, path) =>
|
|
patterns.some(pattern => minimatch(path, pattern, { matchBase: true }));
|
|
|
|
//
|
|
// API interactions
|
|
|
|
const defaultContentHeaders = {
|
|
Accept: 'application/vnd.git-lfs+json',
|
|
['Content-Type']: 'application/vnd.git-lfs+json',
|
|
};
|
|
|
|
const resourceExists = async ({ rootURL, makeAuthorizedRequest }, { sha, size }) => {
|
|
const response = await makeAuthorizedRequest({
|
|
url: `${rootURL}/verify`,
|
|
method: 'POST',
|
|
headers: defaultContentHeaders,
|
|
body: JSON.stringify({ oid: sha, size }),
|
|
});
|
|
if (response.ok) {
|
|
return true;
|
|
}
|
|
if (response.status === 404) {
|
|
return false;
|
|
}
|
|
|
|
// TODO: what kind of error to throw here? APIError doesn't seem
|
|
// to fit
|
|
};
|
|
|
|
const getDownloadURL = ({ rootURL, transformImages: t, makeAuthorizedRequest }, { sha }) =>
|
|
makeAuthorizedRequest(
|
|
`${rootURL}/origin/${sha}${
|
|
t && Object.keys(t).length > 0 ? `?nf_resize=${t.nf_resize}&w=${t.w}&h=${t.h}` : ''
|
|
}`,
|
|
)
|
|
.then(res => (res.ok ? res : Promise.reject(res)))
|
|
.then(res => res.blob())
|
|
.then(blob => URL.createObjectURL(blob))
|
|
.catch(err => console.error(err) || Promise.resolve(''));
|
|
|
|
const getResourceDownloadURLArgs = (clientConfig, objects) => {
|
|
return Promise.resolve(objects.map(({ sha }) => [sha, { sha }]));
|
|
};
|
|
|
|
const getResourceDownloadURLs = (clientConfig, objects) =>
|
|
getResourceDownloadURLArgs(clientConfig, objects)
|
|
.then(map(downloadURLArg => getDownloadURL(downloadURLArg)))
|
|
.then(Promise.all.bind(Promise));
|
|
|
|
const uploadOperation = objects => ({
|
|
operation: 'upload',
|
|
transfers: ['basic'],
|
|
objects: objects.map(({ sha, ...rest }) => ({ ...rest, oid: sha })),
|
|
});
|
|
|
|
const getResourceUploadURLs = async ({ rootURL, makeAuthorizedRequest }, objects) => {
|
|
const response = await makeAuthorizedRequest({
|
|
url: `${rootURL}/objects/batch`,
|
|
method: 'POST',
|
|
headers: defaultContentHeaders,
|
|
body: JSON.stringify(uploadOperation(objects)),
|
|
});
|
|
return (await response.json()).objects.map(object => {
|
|
if (object.error) {
|
|
throw new Error(object.error.message);
|
|
}
|
|
return object.actions.upload.href;
|
|
});
|
|
};
|
|
|
|
const uploadBlob = (clientConfig, uploadURL, blob) =>
|
|
fetch(uploadURL, {
|
|
method: 'PUT',
|
|
body: blob,
|
|
});
|
|
|
|
const uploadResource = async (clientConfig, { sha, size }, resource) => {
|
|
const existingFile = await resourceExists(clientConfig, { sha, size });
|
|
if (existingFile) {
|
|
return sha;
|
|
}
|
|
const [uploadURL] = await getResourceUploadURLs(clientConfig, [{ sha, size }]);
|
|
await uploadBlob(clientConfig, uploadURL, resource);
|
|
return sha;
|
|
};
|
|
|
|
//
|
|
// Create Large Media client
|
|
|
|
const configureFn = (config, fn) => (...args) => fn(config, ...args);
|
|
const clientFns = {
|
|
resourceExists,
|
|
getResourceUploadURLs,
|
|
getResourceDownloadURLs,
|
|
getResourceDownloadURLArgs,
|
|
getDownloadURL,
|
|
uploadResource,
|
|
matchPath,
|
|
};
|
|
export const getClient = clientConfig => {
|
|
return flow([
|
|
Object.keys,
|
|
map(key => [key, configureFn(clientConfig, clientFns[key])]),
|
|
fromPairs,
|
|
configuredFns => ({
|
|
...configuredFns,
|
|
patterns: clientConfig.patterns,
|
|
enabled: clientConfig.enabled,
|
|
}),
|
|
])(clientFns);
|
|
};
|