mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
815c29cf82
[overleaf-editor-core+project-history] Filter tracked changes when fetching files GitOrigin-RevId: 935e4c4712f31b77070aec545a849fc6fefedcd9
173 lines
5.1 KiB
JavaScript
173 lines
5.1 KiB
JavaScript
// @ts-check
|
|
import Core from 'overleaf-editor-core'
|
|
import { Readable as StringStream } from 'stream'
|
|
import BPromise from 'bluebird'
|
|
import OError from '@overleaf/o-error'
|
|
import * as HistoryStoreManager from './HistoryStoreManager.js'
|
|
import * as WebApiManager from './WebApiManager.js'
|
|
import * as Errors from './Errors.js'
|
|
|
|
/** @typedef {import('overleaf-editor-core').Snapshot} Snapshot */
|
|
|
|
StringStream.prototype._read = function () {}
|
|
|
|
const MAX_REQUESTS = 4 // maximum number of parallel requests to v1 history service
|
|
|
|
/**
|
|
*
|
|
* @param {string} projectId
|
|
* @param {number} version
|
|
* @param {string} pathname
|
|
* @param {Function} callback
|
|
*/
|
|
export function getFileSnapshotStream(projectId, version, pathname, callback) {
|
|
_getSnapshotAtVersion(projectId, version, (error, snapshot) => {
|
|
if (error) {
|
|
return callback(OError.tag(error))
|
|
}
|
|
const file = snapshot.getFile(pathname)
|
|
if (file == null) {
|
|
error = new Errors.NotFoundError(`${pathname} not found`, {
|
|
projectId,
|
|
version,
|
|
pathname,
|
|
})
|
|
return callback(error)
|
|
}
|
|
|
|
WebApiManager.getHistoryId(projectId, (err, historyId) => {
|
|
if (err) {
|
|
return callback(OError.tag(err))
|
|
}
|
|
if (file.isEditable()) {
|
|
file
|
|
.load('eager', HistoryStoreManager.getBlobStore(historyId))
|
|
.then(() => {
|
|
const stream = new StringStream()
|
|
stream.push(file.getContent({ filterTrackedDeletes: true }))
|
|
stream.push(null)
|
|
callback(null, stream)
|
|
})
|
|
.catch(err => callback(err))
|
|
} else {
|
|
HistoryStoreManager.getProjectBlobStream(
|
|
historyId,
|
|
file.getHash(),
|
|
callback
|
|
)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
// Returns project snapshot containing the document content for files with
|
|
// text operations in the relevant chunk, and hashes for unmodified/binary
|
|
// files. Used by git bridge to get the state of the project.
|
|
export function getProjectSnapshot(projectId, version, callback) {
|
|
_getSnapshotAtVersion(projectId, version, (error, snapshot) => {
|
|
if (error) {
|
|
return callback(OError.tag(error))
|
|
}
|
|
WebApiManager.getHistoryId(projectId, (err, historyId) => {
|
|
if (err) {
|
|
return callback(OError.tag(err))
|
|
}
|
|
_loadFilesLimit(
|
|
snapshot,
|
|
'eager',
|
|
HistoryStoreManager.getBlobStore(historyId)
|
|
)
|
|
.then(() => {
|
|
const data = {
|
|
projectId,
|
|
files: snapshot.getFileMap().map(file => {
|
|
if (!file) {
|
|
return null
|
|
}
|
|
const content = file.getContent({
|
|
filterTrackedDeletes: true,
|
|
})
|
|
if (content === null) {
|
|
return { data: { hash: file.getHash() } }
|
|
}
|
|
return { data: { content } }
|
|
}),
|
|
}
|
|
callback(null, data)
|
|
})
|
|
.catch(callback)
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} projectId
|
|
* @param {number} version
|
|
* @param {Function} callback
|
|
*/
|
|
function _getSnapshotAtVersion(projectId, version, callback) {
|
|
WebApiManager.getHistoryId(projectId, (error, historyId) => {
|
|
if (error) {
|
|
return callback(OError.tag(error))
|
|
}
|
|
HistoryStoreManager.getChunkAtVersion(
|
|
projectId,
|
|
historyId,
|
|
version,
|
|
(error, data) => {
|
|
if (error) {
|
|
return callback(OError.tag(error))
|
|
}
|
|
const chunk = Core.Chunk.fromRaw(data.chunk)
|
|
const snapshot = chunk.getSnapshot()
|
|
const changes = chunk
|
|
.getChanges()
|
|
.slice(0, version - chunk.getStartVersion())
|
|
snapshot.applyAll(changes)
|
|
callback(null, snapshot)
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
export function getLatestSnapshot(projectId, historyId, callback) {
|
|
HistoryStoreManager.getMostRecentChunk(projectId, historyId, (err, data) => {
|
|
if (err) {
|
|
return callback(err)
|
|
}
|
|
if (data == null || data.chunk == null) {
|
|
return callback(new OError('undefined chunk'))
|
|
}
|
|
// apply all the changes in the chunk to get the current snapshot
|
|
const chunk = Core.Chunk.fromRaw(data.chunk)
|
|
const snapshot = chunk.getSnapshot()
|
|
const changes = chunk.getChanges()
|
|
snapshot.applyAll(changes)
|
|
snapshot
|
|
.loadFiles('lazy', HistoryStoreManager.getBlobStore(historyId))
|
|
.then(snapshotFiles => callback(null, snapshotFiles))
|
|
.catch(err => callback(err))
|
|
})
|
|
}
|
|
|
|
function _loadFilesLimit(snapshot, kind, blobStore) {
|
|
// bluebird promises only support a limit on concurrency for map()
|
|
// so make an array of the files we need to load
|
|
const fileList = []
|
|
snapshot.fileMap.map(file => fileList.push(file))
|
|
// load the files in parallel with a limit on the concurrent requests
|
|
return BPromise.map(
|
|
fileList,
|
|
file => {
|
|
// only load changed files or files with tracked changes, others can be
|
|
// dereferenced from their blobs (this method is only used by the git
|
|
// bridge which understands how to load blobs).
|
|
if (!file.isEditable() || (file.getHash() && !file.getRangesHash())) {
|
|
return
|
|
}
|
|
return file.load(kind, blobStore)
|
|
},
|
|
{ concurrency: MAX_REQUESTS }
|
|
)
|
|
}
|