diff --git a/packages/decap-cms-backend-gitlab/src/API.ts b/packages/decap-cms-backend-gitlab/src/API.ts index e44d151ea986..806c469cba2b 100644 --- a/packages/decap-cms-backend-gitlab/src/API.ts +++ b/packages/decap-cms-backend-gitlab/src/API.ts @@ -346,18 +346,19 @@ export default class API { readFile = async ( path: string, sha?: string | null, - { parseText = true, branch = this.branch } = {}, + { parseText = true, branch = this.branch, lfs = false } = {}, ): Promise => { const fetchContent = async () => { const content = await this.request({ url: `${this.repoURL}/repository/files/${encodeURIComponent(path)}/raw`, - params: { ref: branch }, + params: { ref: branch, ...(lfs ? { lfs: true } : {}) }, cache: 'no-store', }).then(parseText ? this.responseToText : this.responseToBlob); return content; }; - const content = await readFile(sha, fetchContent, localForage, parseText); + const cacheKey = sha && lfs ? `${sha}.lfs` : sha; + const content = await readFile(cacheKey, fetchContent, localForage, parseText); return content; }; diff --git a/packages/decap-cms-backend-gitlab/src/__tests__/API.spec.js b/packages/decap-cms-backend-gitlab/src/__tests__/API.spec.js index ea6c6d9ac6ff..e02ebf48dc19 100644 --- a/packages/decap-cms-backend-gitlab/src/__tests__/API.spec.js +++ b/packages/decap-cms-backend-gitlab/src/__tests__/API.spec.js @@ -145,6 +145,41 @@ describe('GitLab API', () => { }); }); + describe('readFile', () => { + test('does not request GitLab LFS content by default', async () => { + const api = new API({ repo: 'foo/bar' }); + + api.request = jest.fn().mockResolvedValue({}); + api.responseToText = jest.fn().mockReturnValue('file content'); + + await expect(api.readFile('static/uploads/image.png', null)).resolves.toBe('file content'); + + expect(api.request).toHaveBeenCalledWith({ + url: '/projects/foo%2Fbar/repository/files/static%2Fuploads%2Fimage.png/raw', + params: { ref: 'master' }, + cache: 'no-store', + }); + }); + + test('requests GitLab LFS content when requested', async () => { + const api = new API({ repo: 'foo/bar' }); + const blob = new Blob(['image content']); + + api.request = jest.fn().mockResolvedValue({}); + api.responseToBlob = jest.fn().mockReturnValue(blob); + + await expect( + api.readFile('static/uploads/image.png', null, { parseText: false, lfs: true }), + ).resolves.toBe(blob); + + expect(api.request).toHaveBeenCalledWith({ + url: '/projects/foo%2Fbar/repository/files/static%2Fuploads%2Fimage.png/raw', + params: { ref: 'master', lfs: true }, + cache: 'no-store', + }); + }); + }); + describe('getStatuses', () => { test('should get preview statuses', async () => { const api = new API({ repo: 'repo' }); diff --git a/packages/decap-cms-backend-gitlab/src/__tests__/gitlab.spec.js b/packages/decap-cms-backend-gitlab/src/__tests__/gitlab.spec.js index 4953ac3e4a0d..8176e7dc892b 100644 --- a/packages/decap-cms-backend-gitlab/src/__tests__/gitlab.spec.js +++ b/packages/decap-cms-backend-gitlab/src/__tests__/gitlab.spec.js @@ -419,6 +419,77 @@ describe('gitlab backend', () => { }); }); + describe('media files', () => { + it('requests GitLab LFS content for media display files', async () => { + backend = resolveBackend(defaultConfig); + const blob = new Blob(['image content']); + const readFile = jest.fn().mockResolvedValue(blob); + backend.implementation.api = { readFile }; + global.URL.createObjectURL = jest.fn().mockReturnValue('blob:http://localhost/image'); + + await expect( + backend.implementation.getMediaDisplayURL({ + id: 'image-sha', + path: 'static/uploads/image.png', + }), + ).resolves.toBe('blob:http://localhost/image'); + + expect(readFile).toHaveBeenCalledWith('static/uploads/image.png', 'image-sha', { + parseText: false, + lfs: true, + }); + }); + + it('requests GitLab LFS content when downloading media files', async () => { + backend = resolveBackend(defaultConfig); + const blob = new Blob(['image content']); + const readFile = jest.fn().mockResolvedValue(blob); + backend.implementation.api = { readFile }; + global.URL.createObjectURL = jest.fn().mockReturnValue('blob:http://localhost/image'); + + await expect( + backend.implementation.getMediaFile('static/uploads/image.png'), + ).resolves.toEqual( + expect.objectContaining({ + displayURL: 'blob:http://localhost/image', + path: 'static/uploads/image.png', + name: 'image.png', + }), + ); + + expect(readFile).toHaveBeenCalledWith('static/uploads/image.png', null, { + parseText: false, + lfs: true, + }); + }); + + it('requests GitLab LFS content when loading unpublished entry media files', async () => { + backend = resolveBackend(defaultConfig); + const blob = new Blob(['image content']); + const readFile = jest.fn().mockResolvedValue(blob); + backend.implementation.api = { readFile }; + global.URL.createObjectURL = jest.fn().mockReturnValue('blob:http://localhost/image'); + + await expect( + backend.implementation.loadMediaFile('cms/posts/example', { + path: 'static/uploads/image.png', + }), + ).resolves.toEqual( + expect.objectContaining({ + displayURL: 'blob:http://localhost/image', + path: 'static/uploads/image.png', + name: 'image.png', + }), + ); + + expect(readFile).toHaveBeenCalledWith('static/uploads/image.png', null, { + branch: 'cms/posts/example', + parseText: false, + lfs: true, + }); + }); + }); + describe('listEntries', () => { sharedSetup(); diff --git a/packages/decap-cms-backend-gitlab/src/implementation.ts b/packages/decap-cms-backend-gitlab/src/implementation.ts index e629316e1572..3cfa3692be92 100644 --- a/packages/decap-cms-backend-gitlab/src/implementation.ts +++ b/packages/decap-cms-backend-gitlab/src/implementation.ts @@ -265,16 +265,23 @@ export default class GitLab implements Implementation { getMediaDisplayURL(displayURL: DisplayURL) { this._mediaDisplayURLSem = this._mediaDisplayURLSem || semaphore(MAX_CONCURRENT_DOWNLOADS); - return getMediaDisplayURL( - displayURL, - this.api!.readFile.bind(this.api!), - this._mediaDisplayURLSem, - ); + const readMediaFile = ( + path: string, + id: string | null | undefined, + { parseText }: { parseText: boolean }, + ) => this.api!.readFile(path, id, { parseText, lfs: true }); + + return getMediaDisplayURL(displayURL, readMediaFile, this._mediaDisplayURLSem); } async getMediaFile(path: string) { const name = basename(path); - const blob = await getMediaAsBlob(path, null, this.api!.readFile.bind(this.api!)); + const readMediaFile = ( + path: string, + id: string | null | undefined, + { parseText }: { parseText: boolean }, + ) => this.api!.readFile(path, id, { parseText, lfs: true }); + const blob = await getMediaAsBlob(path, null, readMediaFile); const fileObj = blobToFileObj(name, blob); const url = URL.createObjectURL(fileObj); const id = await getBlobSHA(blob); @@ -354,7 +361,7 @@ export default class GitLab implements Implementation { path: string, id: string | null | undefined, { parseText }: { parseText: boolean }, - ) => this.api!.readFile(path, id, { branch, parseText }); + ) => this.api!.readFile(path, id, { branch, parseText, lfs: true }); return getMediaAsBlob(file.path, null, readFile).then(blob => { const name = basename(file.path);