feat(workspace): add blob and storage limit (#5535)

close TOV-343 AFF-508 TOV-461 TOV-460 TOV-419

Add `isOverCapacity ` status to detect if blob usage exceeds limits.
Add `onCapacityChange` and `onBlobSet` to monitor if the storage or blob exceeds the capacity limit.
Global modals `LocalQuotaModal` and `CloudQuotaModal` have been added, with the upload size of the blob being limited within the modal components.
The notification component has been adjusted, now you can pass in `action` click events and `actionLabel` .
This commit is contained in:
JimmFly
2024-01-24 07:34:51 +00:00
parent c566952e09
commit 25897dc404
25 changed files with 394 additions and 58 deletions

View File

@@ -1,8 +1,15 @@
import { DebugLogger } from '@affine/debug';
import { Slot } from '@blocksuite/global/utils';
import { difference } from 'lodash-es';
import { BlobStorageOverCapacity } from './error';
const logger = new DebugLogger('affine:blob-engine');
export interface BlobStatus {
isStorageOverCapacity: boolean;
}
/**
* # BlobEngine
*
@@ -12,6 +19,19 @@ const logger = new DebugLogger('affine:blob-engine');
*/
export class BlobEngine {
private abort: AbortController | null = null;
private _status: BlobStatus = { isStorageOverCapacity: false };
onStatusChange = new Slot<BlobStatus>();
singleBlobSizeLimit: number = 100 * 1024 * 1024;
onAbortLargeBlob = new Slot<Blob>();
private set status(s: BlobStatus) {
logger.debug('status change', s);
this._status = s;
this.onStatusChange.emit(s);
}
get status() {
return this._status;
}
constructor(
private readonly local: BlobStorage,
@@ -53,7 +73,7 @@ export class BlobEngine {
}
async sync() {
if (this.local.readonly) {
if (this.local.readonly || this._status.isStorageOverCapacity) {
return;
}
logger.debug('start syncing blob...');
@@ -78,6 +98,11 @@ export class BlobEngine {
await remote.set(key, data);
}
} catch (err) {
if (err instanceof BlobStorageOverCapacity) {
this.status = {
isStorageOverCapacity: true,
};
}
logger.error(
`error when sync ${key} from [${this.local.name}] to [${remote.name}]`,
err
@@ -122,6 +147,12 @@ export class BlobEngine {
throw new Error('local peer is readonly');
}
if (value.size > this.singleBlobSizeLimit) {
this.onAbortLargeBlob.emit(value);
logger.error('blob over limit, abort set');
return key;
}
// await upload to the local peer
await this.local.set(key, value);
@@ -131,7 +162,7 @@ export class BlobEngine {
.filter(r => !r.readonly)
.map(peer =>
peer.set(key, value).catch(err => {
logger.error('error when upload to peer', err);
logger.error('Error when uploading to peer', err);
})
)
)

View File

@@ -0,0 +1,5 @@
export class BlobStorageOverCapacity extends Error {
constructor(public originError?: any) {
super('Blob storage over capacity.');
}
}

View File

@@ -2,11 +2,12 @@ import { Slot } from '@blocksuite/global/utils';
import { throwIfAborted } from '../utils/throw-if-aborted';
import type { AwarenessProvider } from './awareness';
import type { BlobEngine } from './blob';
import type { BlobEngine, BlobStatus } from './blob';
import type { SyncEngine, SyncEngineStatus } from './sync';
export interface WorkspaceEngineStatus {
sync: SyncEngineStatus;
blob: BlobStatus;
}
/**
@@ -34,10 +35,18 @@ export class WorkspaceEngine {
) {
this._status = {
sync: sync.status,
blob: blob.status,
};
sync.onStatusChange.on(status => {
this.status = {
sync: status,
blob: blob.status,
};
});
blob.onStatusChange.on(status => {
this.status = {
sync: sync.status,
blob: status,
};
});
}
@@ -71,4 +80,5 @@ export class WorkspaceEngine {
export * from './awareness';
export * from './blob';
export * from './error';
export * from './sync';