mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix(app): stop aborting shared uploads when cancelling one attachment (#1967)
This commit is contained in:
1 parent
de97bf908d
commit
51e2eee15a
2 files changed
+49
-29
No files matched your search
@@ -22,6 +22,7 @@ import {
|
|||||||
resolveMessageQueueRequestOutcomeDecision,
|
resolveMessageQueueRequestOutcomeDecision,
|
||||||
resolveMessageQueueSendExecutionDecision,
|
resolveMessageQueueSendExecutionDecision,
|
||||||
} from '@app/features/messaging/state/MessageQueueStateMachine';
|
} from '@app/features/messaging/state/MessageQueueStateMachine';
|
||||||
|
import {planTextareaAttachmentCancellation} from '@app/features/messaging/state/TextareaAttachmentUploadCancellation';
|
||||||
import {
|
import {
|
||||||
type ChunkedUploadPart,
|
type ChunkedUploadPart,
|
||||||
type ChunkedUploadPlan,
|
type ChunkedUploadPlan,
|
||||||
@@ -50,7 +51,7 @@ import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
|
|||||||
import {modal} from '@app/features/ui/commands/ModalCommands';
|
import {modal} from '@app/features/ui/commands/ModalCommands';
|
||||||
import {formatUserSettingsPath} from '@app/features/user/components/settings_utils/SettingsConstants';
|
import {formatUserSettingsPath} from '@app/features/user/components/settings_utils/SettingsConstants';
|
||||||
import PrivacyPreferences from '@app/features/user/state/PrivacyPreferences';
|
import PrivacyPreferences from '@app/features/user/state/PrivacyPreferences';
|
||||||
import {Queue, type QueueEntry} from '@app/lib/list/ListQueue';
|
import {Queue} from '@app/lib/list/ListQueue';
|
||||||
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
||||||
import type {
|
import type {
|
||||||
AllowedMentions,
|
AllowedMentions,
|
||||||
@@ -254,10 +255,6 @@ const isNetworkRequestError = (error: unknown): boolean => {
|
|||||||
return error instanceof Error && error.message === 'Network error during request';
|
return error instanceof Error && error.message === 'Network error during request';
|
||||||
};
|
};
|
||||||
|
|
||||||
function isSendPayload(payload: MessageQueuePayload): payload is SendMessagePayload {
|
|
||||||
return payload.type === 'send';
|
|
||||||
}
|
|
||||||
|
|
||||||
function isRateLimitError(error: HttpError): boolean {
|
function isRateLimitError(error: HttpError): boolean {
|
||||||
if (error.status !== 429) return false;
|
if (error.status !== 429) return false;
|
||||||
return !isSlowmodeError(error);
|
return !isSlowmodeError(error);
|
||||||
@@ -516,23 +513,6 @@ export class MessageQueue extends Queue<MessageQueuePayload, RestResponse<Messag
|
|||||||
this.abortControllers.delete(nonce);
|
this.abortControllers.delete(nonce);
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelPendingSendRequests(channelId: string): Array<SendMessagePayload> {
|
|
||||||
const cancelled: Array<SendMessagePayload> = [];
|
|
||||||
const remaining: Array<QueueEntry<MessageQueuePayload, RestResponse<Message> | undefined>> = [];
|
|
||||||
while (this.queue.length > 0) {
|
|
||||||
const entry = this.queue.shift()!;
|
|
||||||
if (isSendPayload(entry.message) && entry.message.channelId === channelId) {
|
|
||||||
cancelled.push(entry.message);
|
|
||||||
this.cancelRequest(entry.message.nonce);
|
|
||||||
} else {
|
|
||||||
remaining.push(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.queue.push(...remaining);
|
|
||||||
logger.info('Cancel pending send requests', cancelled.length);
|
|
||||||
return cancelled;
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendImmediately(payload: SendMessagePayload): Promise<RestResponse<Message> | undefined> {
|
async sendImmediately(payload: SendMessagePayload): Promise<RestResponse<Message> | undefined> {
|
||||||
while (true) {
|
while (true) {
|
||||||
const completion = await this.drainSendImmediately(payload);
|
const completion = await this.drainSendImmediately(payload);
|
||||||
@@ -713,16 +693,15 @@ export class MessageQueue extends Queue<MessageQueuePayload, RestResponse<Messag
|
|||||||
}
|
}
|
||||||
|
|
||||||
private cancelTextareaAttachmentUploads(attachmentIds: ReadonlyArray<number>): void {
|
private cancelTextareaAttachmentUploads(attachmentIds: ReadonlyArray<number>): void {
|
||||||
const channelIds = new Set<string>();
|
const plan = planTextareaAttachmentCancellation(this.textareaAttachmentUploads, attachmentIds);
|
||||||
for (const attachmentId of attachmentIds) {
|
for (const {attachmentId, upload} of plan.cancelled) {
|
||||||
const upload = this.textareaAttachmentUploads.get(attachmentId);
|
|
||||||
if (!upload) continue;
|
|
||||||
channelIds.add(upload.channelId);
|
|
||||||
upload.requestAbortController.abort();
|
|
||||||
upload.abortController.abort();
|
upload.abortController.abort();
|
||||||
this.textareaAttachmentUploads.delete(attachmentId);
|
this.textareaAttachmentUploads.delete(attachmentId);
|
||||||
}
|
}
|
||||||
for (const channelId of channelIds) {
|
for (const controller of plan.requestControllersToAbort) {
|
||||||
|
controller.abort();
|
||||||
|
}
|
||||||
|
for (const channelId of plan.channelIds) {
|
||||||
this.disposeTextareaAttachmentUploadPrunerIfIdle(channelId);
|
this.disposeTextareaAttachmentUploadPrunerIfIdle(channelId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
export interface CancellableTextareaAttachmentUpload {
|
||||||
|
channelId: string;
|
||||||
|
requestAbortController: AbortController;
|
||||||
|
abortController: AbortController;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextareaAttachmentCancellationPlan<TUpload> {
|
||||||
|
cancelled: Array<{attachmentId: number; upload: TUpload}>;
|
||||||
|
requestControllersToAbort: Array<AbortController>;
|
||||||
|
channelIds: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function planTextareaAttachmentCancellation<TUpload extends CancellableTextareaAttachmentUpload>(
|
||||||
|
uploads: ReadonlyMap<number, TUpload>,
|
||||||
|
attachmentIds: ReadonlyArray<number>,
|
||||||
|
): TextareaAttachmentCancellationPlan<TUpload> {
|
||||||
|
const cancelled: Array<{attachmentId: number; upload: TUpload}> = [];
|
||||||
|
const cancelledIds = new Set<number>();
|
||||||
|
const channelIds = new Set<string>();
|
||||||
|
const releasedRequestControllers = new Set<AbortController>();
|
||||||
|
for (const attachmentId of attachmentIds) {
|
||||||
|
if (cancelledIds.has(attachmentId)) continue;
|
||||||
|
const upload = uploads.get(attachmentId);
|
||||||
|
if (!upload) continue;
|
||||||
|
cancelledIds.add(attachmentId);
|
||||||
|
cancelled.push({attachmentId, upload});
|
||||||
|
channelIds.add(upload.channelId);
|
||||||
|
releasedRequestControllers.add(upload.requestAbortController);
|
||||||
|
}
|
||||||
|
for (const [attachmentId, upload] of uploads) {
|
||||||
|
if (cancelledIds.has(attachmentId)) continue;
|
||||||
|
releasedRequestControllers.delete(upload.requestAbortController);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
cancelled,
|
||||||
|
requestControllersToAbort: Array.from(releasedRequestControllers),
|
||||||
|
channelIds: Array.from(channelIds),
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in new issue
Block a user