mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-25 14:28:51 +08:00
fix(server): avoid get object content when syncing (#9402)
This commit is contained in:
@@ -24,6 +24,10 @@ export interface WorkspaceEvents {
|
|||||||
workspaceId: Workspace['id'];
|
workspaceId: Workspace['id'];
|
||||||
key: string;
|
key: string;
|
||||||
}>;
|
}>;
|
||||||
|
sync: Payload<{
|
||||||
|
workspaceId: Workspace['id'];
|
||||||
|
key: string;
|
||||||
|
}>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,15 @@ export class FsStorageProvider implements StorageProvider {
|
|||||||
this.logger.verbose(`Object \`${key}\` put`);
|
this.logger.verbose(`Object \`${key}\` put`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async head(key: string) {
|
||||||
|
const metadata = this.readMetadata(key);
|
||||||
|
if (!metadata) {
|
||||||
|
this.logger.verbose(`Object \`${key}\` not found`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
async get(key: string): Promise<{
|
async get(key: string): Promise<{
|
||||||
body?: Readable;
|
body?: Readable;
|
||||||
metadata?: GetObjectMetadata;
|
metadata?: GetObjectMetadata;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export interface StorageProvider {
|
|||||||
body: BlobInputType,
|
body: BlobInputType,
|
||||||
metadata?: PutObjectMetadata
|
metadata?: PutObjectMetadata
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
head(key: string): Promise<GetObjectMetadata | undefined>;
|
||||||
get(
|
get(
|
||||||
key: string
|
key: string
|
||||||
): Promise<{ body?: BlobOutputType; metadata?: GetObjectMetadata }>;
|
): Promise<{ body?: BlobOutputType; metadata?: GetObjectMetadata }>;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export class WorkspaceBlobStorage {
|
|||||||
const meta: PutObjectMetadata = autoMetadata(blob);
|
const meta: PutObjectMetadata = autoMetadata(blob);
|
||||||
|
|
||||||
await this.provider.put(`${workspaceId}/${key}`, blob, meta);
|
await this.provider.put(`${workspaceId}/${key}`, blob, meta);
|
||||||
this.trySyncBlobMeta(workspaceId, key, {
|
await this.upsert(workspaceId, key, {
|
||||||
contentType: meta.contentType ?? 'application/octet-stream',
|
contentType: meta.contentType ?? 'application/octet-stream',
|
||||||
contentLength: blob.length,
|
contentLength: blob.length,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
@@ -119,53 +119,48 @@ export class WorkspaceBlobStorage {
|
|||||||
|
|
||||||
private trySyncBlobsMeta(workspaceId: string, blobs: ListObjectsMetadata[]) {
|
private trySyncBlobsMeta(workspaceId: string, blobs: ListObjectsMetadata[]) {
|
||||||
for (const blob of blobs) {
|
for (const blob of blobs) {
|
||||||
this.trySyncBlobMeta(workspaceId, blob.key);
|
this.event.emit('workspace.blob.sync', {
|
||||||
|
workspaceId,
|
||||||
|
key: blob.key,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private trySyncBlobMeta(
|
private async upsert(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
key: string,
|
key: string,
|
||||||
meta?: GetObjectMetadata
|
meta: GetObjectMetadata
|
||||||
) {
|
) {
|
||||||
setImmediate(() => {
|
await this.db.blob.upsert({
|
||||||
this.syncBlobMeta(workspaceId, key, meta).catch(() => {
|
where: {
|
||||||
/* never throw */
|
workspaceId_key: {
|
||||||
});
|
workspaceId,
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
mime: meta.contentType,
|
||||||
|
size: meta.contentLength,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
workspaceId,
|
||||||
|
key,
|
||||||
|
mime: meta.contentType,
|
||||||
|
size: meta.contentLength,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async syncBlobMeta(
|
@OnEvent('workspace.blob.sync')
|
||||||
workspaceId: string,
|
async syncBlobMeta({
|
||||||
key: string,
|
workspaceId,
|
||||||
meta?: GetObjectMetadata
|
key,
|
||||||
) {
|
}: EventPayload<'workspace.blob.sync'>) {
|
||||||
try {
|
try {
|
||||||
if (!meta) {
|
const meta = await this.provider.head(`${workspaceId}/${key}`);
|
||||||
const blob = await this.get(workspaceId, key);
|
|
||||||
meta = blob.metadata;
|
|
||||||
blob.body?.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (meta) {
|
if (meta) {
|
||||||
await this.db.blob.upsert({
|
await this.upsert(workspaceId, key, meta);
|
||||||
where: {
|
|
||||||
workspaceId_key: {
|
|
||||||
workspaceId,
|
|
||||||
key,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
mime: meta.contentType,
|
|
||||||
size: meta.contentLength,
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
workspaceId,
|
|
||||||
key,
|
|
||||||
mime: meta.contentType,
|
|
||||||
size: meta.contentLength,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
await this.db.blob.deleteMany({
|
await this.db.blob.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* oxlint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
import { Readable } from 'node:stream';
|
import { Readable } from 'node:stream';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DeleteObjectCommand,
|
DeleteObjectCommand,
|
||||||
GetObjectCommand,
|
GetObjectCommand,
|
||||||
|
HeadObjectCommand,
|
||||||
ListObjectsV2Command,
|
ListObjectsV2Command,
|
||||||
NoSuchKey,
|
NoSuchKey,
|
||||||
PutObjectCommand,
|
PutObjectCommand,
|
||||||
@@ -75,6 +76,34 @@ export class S3StorageProvider implements StorageProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async head(key: string) {
|
||||||
|
try {
|
||||||
|
const obj = await this.client.send(
|
||||||
|
new HeadObjectCommand({
|
||||||
|
Bucket: this.bucket,
|
||||||
|
Key: key,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
contentType: obj.ContentType!,
|
||||||
|
contentLength: obj.ContentLength!,
|
||||||
|
lastModified: obj.LastModified!,
|
||||||
|
checksumCRC32: obj.ChecksumCRC32,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
// 404
|
||||||
|
if (e instanceof NoSuchKey) {
|
||||||
|
this.logger.verbose(`Object \`${key}\` not found`);
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to head object \`${key}\``, {
|
||||||
|
cause: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async get(key: string): Promise<{
|
async get(key: string): Promise<{
|
||||||
body?: Readable;
|
body?: Readable;
|
||||||
metadata?: GetObjectMetadata;
|
metadata?: GetObjectMetadata;
|
||||||
|
|||||||
Reference in New Issue
Block a user