|
|
@@ -7,6 +7,7 @@ import EditorController from '../Editor/EditorController.js'
|
|
|
import Errors from '../Errors/Errors.js'
|
|
|
import moment from 'moment'
|
|
|
import { callbackifyAll } from '@overleaf/promise-utils'
|
|
|
+import { fetchJson } from '@overleaf/fetch-utils'
|
|
|
import ProjectLocator from '../Project/ProjectLocator.js'
|
|
|
import DocumentUpdaterHandler from '../DocumentUpdater/DocumentUpdaterHandler.js'
|
|
|
import ChatApiHandler from '../Chat/ChatApiHandler.js'
|
|
|
@@ -68,13 +69,6 @@ const RestoreManager = {
|
|
|
)
|
|
|
const snapshot = Snapshot.fromRaw(snapshotRaw)
|
|
|
|
|
|
- const origin = options.origin ?? {
|
|
|
- kind: 'file-restore',
|
|
|
- path: pathname,
|
|
|
- version,
|
|
|
- timestamp: snapshot.getTimestamp()?.toISOString(),
|
|
|
- }
|
|
|
-
|
|
|
return await RestoreManager._revertSingleFile(
|
|
|
userId,
|
|
|
projectId,
|
|
|
@@ -82,7 +76,7 @@ const RestoreManager = {
|
|
|
pathname,
|
|
|
threadIds,
|
|
|
snapshot,
|
|
|
- { origin }
|
|
|
+ options
|
|
|
)
|
|
|
},
|
|
|
|
|
|
@@ -137,9 +131,17 @@ const RestoreManager = {
|
|
|
})
|
|
|
.catch(() => null)
|
|
|
|
|
|
- const snapshotFile = projectSnapshotAtVersion.getFile(pathname)
|
|
|
- if (!snapshotFile) {
|
|
|
- throw new OError('file not found in snapshot', { pathname })
|
|
|
+ const updates = await RestoreManager._getUpdatesFromHistory(
|
|
|
+ projectId,
|
|
|
+ version
|
|
|
+ )
|
|
|
+ const updateAtVersion = updates.find(update => update.toV === version)
|
|
|
+
|
|
|
+ const origin = options.origin || {
|
|
|
+ kind: 'file-restore',
|
|
|
+ path: pathname,
|
|
|
+ version,
|
|
|
+ timestamp: new Date(updateAtVersion.meta.end_ts).toISOString(),
|
|
|
}
|
|
|
|
|
|
const importInfo = await FileSystemImportManager.promises.importFile(
|
|
|
@@ -160,7 +162,7 @@ const RestoreManager = {
|
|
|
projectId,
|
|
|
file.element._id,
|
|
|
file.type,
|
|
|
- options.origin,
|
|
|
+ origin,
|
|
|
userId
|
|
|
)
|
|
|
|
|
|
@@ -175,6 +177,11 @@ const RestoreManager = {
|
|
|
threadIds.delete(file.element._id.toString())
|
|
|
}
|
|
|
|
|
|
+ const snapshotFile = projectSnapshotAtVersion.getFile(pathname)
|
|
|
+ if (!snapshotFile) {
|
|
|
+ throw new OError('file not found in snapshot', { pathname })
|
|
|
+ }
|
|
|
+
|
|
|
// Look for metadata indicating a linked file.
|
|
|
const fileMetadata = snapshotFile.getMetadata()
|
|
|
const isFileMetadata = fileMetadata && 'provider' in fileMetadata
|
|
|
@@ -188,7 +195,7 @@ const RestoreManager = {
|
|
|
basename,
|
|
|
fsPath,
|
|
|
fileMetadata,
|
|
|
- options.origin,
|
|
|
+ origin,
|
|
|
userId
|
|
|
)
|
|
|
|
|
|
@@ -289,21 +296,13 @@ const RestoreManager = {
|
|
|
newCommentThreadData
|
|
|
)
|
|
|
|
|
|
- // At this point, we know that the file is editable in web. We assume it's
|
|
|
- // also editable in history because the rules for editable files in history
|
|
|
- // are less strict than the rules in web. Because of that, we can call
|
|
|
- // getContent() on the snapshot file.
|
|
|
- const lines = snapshotFile
|
|
|
- .getContent({ filterTrackedDeletes: true })
|
|
|
- .split('\n')
|
|
|
-
|
|
|
const { _id } = await EditorController.promises.addDocWithRanges(
|
|
|
projectId,
|
|
|
parentFolderId,
|
|
|
basename,
|
|
|
- lines,
|
|
|
+ importInfo.lines,
|
|
|
newRanges,
|
|
|
- options.origin,
|
|
|
+ origin,
|
|
|
userId
|
|
|
)
|
|
|
|
|
|
@@ -363,21 +362,31 @@ const RestoreManager = {
|
|
|
throw new OError('project does not have ranges support', { projectId })
|
|
|
}
|
|
|
|
|
|
- const snapshotRaw = await HistoryManager.promises.getContentAtVersion(
|
|
|
+ // Get project paths at version
|
|
|
+ const pathsAtPastVersion = await RestoreManager._getProjectPathsAtVersion(
|
|
|
projectId,
|
|
|
version
|
|
|
)
|
|
|
- const snapshot = Snapshot.fromRaw(snapshotRaw)
|
|
|
|
|
|
- const pathsAtPastVersion = snapshot.getFilePathnames()
|
|
|
+ const updates = await RestoreManager._getUpdatesFromHistory(
|
|
|
+ projectId,
|
|
|
+ version
|
|
|
+ )
|
|
|
+ const updateAtVersion = updates.find(update => update.toV === version)
|
|
|
|
|
|
const origin = {
|
|
|
kind: 'project-restore',
|
|
|
version,
|
|
|
- timestamp: snapshot.getTimestamp()?.toISOString(),
|
|
|
+ timestamp: new Date(updateAtVersion.meta.end_ts).toISOString(),
|
|
|
}
|
|
|
const threadIds = await getCommentThreadIds(projectId)
|
|
|
|
|
|
+ const snapshotRaw = await HistoryManager.promises.getContentAtVersion(
|
|
|
+ projectId,
|
|
|
+ version
|
|
|
+ )
|
|
|
+ const snapshot = Snapshot.fromRaw(snapshotRaw)
|
|
|
+
|
|
|
const reverted = []
|
|
|
for (const pathname of pathsAtPastVersion) {
|
|
|
const res = await RestoreManager._revertSingleFile(
|
|
|
@@ -428,6 +437,18 @@ const RestoreManager = {
|
|
|
}/project/${projectId}/version/${version}/${encodeURIComponent(pathname)}`
|
|
|
return await FileWriter.promises.writeUrlToDisk(projectId, url)
|
|
|
},
|
|
|
+
|
|
|
+ async _getUpdatesFromHistory(projectId, version) {
|
|
|
+ const url = `${Settings.apis.project_history.url}/project/${projectId}/updates?before=${version}&min_count=1`
|
|
|
+ const res = await fetchJson(url)
|
|
|
+ return res.updates
|
|
|
+ },
|
|
|
+
|
|
|
+ async _getProjectPathsAtVersion(projectId, version) {
|
|
|
+ const url = `${Settings.apis.project_history.url}/project/${projectId}/paths/version/${version}`
|
|
|
+ const res = await fetchJson(url)
|
|
|
+ return res.paths
|
|
|
+ },
|
|
|
}
|
|
|
|
|
|
export default { ...callbackifyAll(RestoreManager), promises: RestoreManager }
|