mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-23 05:53:09 +03:00
UBERF-7411: Allow to backup blobs with wrong size (#5926)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
e413cd3b0f
commit
4dcfc5e533
@ -12,8 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import { type Blob, type CollaborativeDoc, type Ref, collaborativeDocParse } from '@hcengineering/core'
|
||||
import { getBlobHref } from '@hcengineering/presentation'
|
||||
import core, {
|
||||
collaborativeDocParse,
|
||||
type Blob,
|
||||
type BlobLookup,
|
||||
type CollaborativeDoc,
|
||||
type Ref
|
||||
} from '@hcengineering/core'
|
||||
import { getBlobHref, getClient } from '@hcengineering/presentation'
|
||||
import { ObservableV2 as Observable } from 'lib0/observable'
|
||||
import { applyUpdate, type Doc as YDoc } from 'yjs'
|
||||
|
||||
@ -33,7 +39,11 @@ async function fetchContent (blob: Ref<Blob>, doc: YDoc): Promise<boolean> {
|
||||
|
||||
async function fetchBlobContent (_id: Ref<Blob>): Promise<Uint8Array | undefined> {
|
||||
try {
|
||||
const href = await getBlobHref(undefined, _id)
|
||||
const blob = (await getClient().findOne(core.class.Blob, { _id })) as BlobLookup
|
||||
if (blob.size === 0) {
|
||||
return undefined
|
||||
}
|
||||
const href = await getBlobHref(blob, _id)
|
||||
const res = await fetch(href)
|
||||
|
||||
if (res.ok) {
|
||||
|
@ -66,7 +66,7 @@
|
||||
opacity: 0;
|
||||
animation-name: makeVisible;
|
||||
animation-duration: 0.25s;
|
||||
animation-delay: 0.1s;
|
||||
animation-delay: 0.25s;
|
||||
animation-fill-mode: forwards;
|
||||
|
||||
&.labeled {
|
||||
|
@ -90,12 +90,12 @@ export async function connect (title: string): Promise<Client | undefined> {
|
||||
setMetadata(presentation.metadata.Token, token)
|
||||
|
||||
const fetchWorkspace = await getResource(login.function.FetchWorkspace)
|
||||
let loginInfo = await ctx.with('select-workspace', {}, async () => (await fetchWorkspace(ws))[1])
|
||||
let loginInfo = await ctx.with('fetch-workspace', {}, async () => (await fetchWorkspace(ws))[1])
|
||||
if (loginInfo?.creating === true) {
|
||||
while (true) {
|
||||
if (ws !== getCurrentLocation().path[1]) return
|
||||
workspaceCreating.set(loginInfo?.createProgress ?? 0)
|
||||
loginInfo = await ctx.with('select-workspace', {}, async () => (await fetchWorkspace(ws))[1])
|
||||
loginInfo = await ctx.with('fetch-workspace', {}, async () => (await fetchWorkspace(ws))[1])
|
||||
workspaceCreating.set(loginInfo?.createProgress)
|
||||
if (loginInfo?.creating === false) {
|
||||
workspaceCreating.set(-1)
|
||||
|
@ -50,7 +50,7 @@ import { getModelVersion } from '@hcengineering/model-all'
|
||||
import platform, { getMetadata, PlatformError, Severity, Status, translate } from '@hcengineering/platform'
|
||||
import { cloneWorkspace } from '@hcengineering/server-backup'
|
||||
import { decodeToken, generateToken } from '@hcengineering/server-token'
|
||||
import toolPlugin, { connect, initModel, upgradeModel, getStorageAdapter } from '@hcengineering/server-tool'
|
||||
import toolPlugin, { connect, getStorageAdapter, initModel, upgradeModel } from '@hcengineering/server-tool'
|
||||
import { pbkdf2Sync, randomBytes } from 'crypto'
|
||||
import { Binary, Db, Filter, ObjectId, type MongoClient } from 'mongodb'
|
||||
import fetch from 'node-fetch'
|
||||
@ -1297,7 +1297,7 @@ export async function getWorkspaceInfo (
|
||||
workspace: workspace.name
|
||||
}
|
||||
if (email !== systemAccountEmail && !guest) {
|
||||
account = await getAccount(db, email)
|
||||
account = await ctx.with('get-account', {}, async () => await getAccount(db, email))
|
||||
if (account === null) {
|
||||
ctx.error('no account', { email, productId, token })
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
@ -1318,15 +1318,19 @@ export async function getWorkspaceInfo (
|
||||
query._id = { $in: account.workspaces }
|
||||
}
|
||||
|
||||
const [ws] = (
|
||||
await db.collection<Workspace>(WORKSPACE_COLLECTION).find(withProductId(productId, query)).toArray()
|
||||
).filter((it) => it.disabled !== true || account?.admin === true || it.creating === true)
|
||||
const [ws] = await ctx.with('get-account', {}, async () =>
|
||||
(await db.collection<Workspace>(WORKSPACE_COLLECTION).find(withProductId(productId, query)).toArray()).filter(
|
||||
(it) => it.disabled !== true || account?.admin === true || it.creating === true
|
||||
)
|
||||
)
|
||||
if (ws == null) {
|
||||
ctx.error('no workspace', { workspace: workspace.name, email })
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
if (_updateLastVisit && isAccount(account)) {
|
||||
await updateLastVisit(db, ws, account)
|
||||
void ctx.with('update-last-visit', {}, async () => {
|
||||
await updateLastVisit(db, ws, account as Account)
|
||||
})
|
||||
}
|
||||
return mapToClientWorkspace(ws)
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import core, {
|
||||
} from '@hcengineering/core'
|
||||
import type { StorageAdapter } from '@hcengineering/server-core'
|
||||
import { BlobClient, connect } from '@hcengineering/server-tool'
|
||||
import { mkdtemp, writeFile } from 'node:fs/promises'
|
||||
import { PassThrough } from 'node:stream'
|
||||
import { createGzip } from 'node:zlib'
|
||||
import { join } from 'path'
|
||||
@ -554,6 +555,8 @@ export async function backup (
|
||||
const blobClient = new BlobClient(transactorUrl, workspaceId)
|
||||
ctx.info('starting backup', { workspace: workspaceId.name })
|
||||
|
||||
let tmpDir: string | undefined
|
||||
|
||||
try {
|
||||
const domains = [
|
||||
...connection
|
||||
@ -867,45 +870,52 @@ export async function backup (
|
||||
}
|
||||
|
||||
let blobFiled = false
|
||||
if (blob.size !== 0 && !(await blobClient.checkFile(ctx, blob._id))) {
|
||||
ctx.error('failed to download blob', { blob: blob._id, provider: blob.provider })
|
||||
processChanges(d, true)
|
||||
continue
|
||||
}
|
||||
|
||||
addedDocuments += descrJson.length
|
||||
addedDocuments += blob.size
|
||||
|
||||
_pack.entry({ name: d._id + '.json' }, descrJson, function (err) {
|
||||
if (err != null) throw err
|
||||
})
|
||||
printDownloaded(blob._id, descrJson.length)
|
||||
try {
|
||||
const entry = _pack?.entry({ name: d._id, size: blob.size }, (err) => {
|
||||
const buffers: Buffer[] = []
|
||||
await blobClient.writeTo(ctx, blob._id, blob.size, {
|
||||
write (buffer, cb) {
|
||||
buffers.push(buffer)
|
||||
cb()
|
||||
},
|
||||
end: (cb: () => void) => {
|
||||
cb()
|
||||
}
|
||||
})
|
||||
|
||||
const finalBuffer = Buffer.concat(buffers)
|
||||
if (finalBuffer.length !== blob.size) {
|
||||
tmpDir = tmpDir ?? (await mkdtemp('backup', {}))
|
||||
const tmpFile = join(tmpDir, blob._id)
|
||||
await writeFile(tmpFile, finalBuffer)
|
||||
await writeFile(tmpFile + '.json', JSON.stringify(blob, undefined, 2))
|
||||
ctx.error('download blob size mismatch', {
|
||||
_id: blob._id,
|
||||
contentType: blob.contentType,
|
||||
size: blob.size,
|
||||
provider: blob.provider,
|
||||
tempDir: tmpDir
|
||||
})
|
||||
}
|
||||
_pack.entry({ name: d._id + '.json' }, descrJson, (err) => {
|
||||
if (err != null) throw err
|
||||
})
|
||||
_pack?.entry({ name: d._id, size: finalBuffer.length }, finalBuffer, (err) => {
|
||||
if (err != null) {
|
||||
ctx.error('error packing file', { err })
|
||||
}
|
||||
})
|
||||
if (blob.size === 0) {
|
||||
entry.end()
|
||||
} else {
|
||||
// if (blob.size > 1024 * 1024) {
|
||||
ctx.info('download blob', {
|
||||
_id: blob._id,
|
||||
contentType: blob.contentType,
|
||||
size: blob.size,
|
||||
provider: blob.provider
|
||||
provider: blob.provider,
|
||||
pending: docs.length
|
||||
})
|
||||
// }
|
||||
await blobClient.writeTo(ctx, blob._id, blob.size, {
|
||||
write (buffer, cb) {
|
||||
entry.write(buffer, cb)
|
||||
},
|
||||
end: (cb: () => void) => {
|
||||
entry.end(cb)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
printDownloaded(blob._id, blob.size)
|
||||
} catch (err: any) {
|
||||
@ -916,6 +926,7 @@ export async function backup (
|
||||
}
|
||||
blobFiled = true
|
||||
}
|
||||
|
||||
processChanges(d, blobFiled)
|
||||
} else {
|
||||
const data = JSON.stringify(d)
|
||||
|
@ -88,6 +88,8 @@ class StorageBlobAdapter implements DbAdapter {
|
||||
blob.contentType = blobStat.contentType
|
||||
blob.version = blobStat.version
|
||||
blob.size = blobStat.size
|
||||
delete (blob as any).downloadUrl
|
||||
delete (blob as any).downloadUrlExpire
|
||||
|
||||
toUpload.push(blob)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user