From 3d51b17e331d642375384cae51560da91489a693 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 20 Aug 2024 14:36:09 +0200 Subject: [PATCH] MOBILE-4482 forum: Avoid reuploading files when editing post --- src/addons/mod/forum/components/post/post.ts | 186 ++++++++++++------- 1 file changed, 122 insertions(+), 64 deletions(-) diff --git a/src/addons/mod/forum/components/post/post.ts b/src/addons/mod/forum/components/post/post.ts index 258b00a22..88acd5990 100644 --- a/src/addons/mod/forum/components/post/post.ts +++ b/src/addons/mod/forum/components/post/post.ts @@ -39,7 +39,7 @@ import { } from '../../services/forum'; import { CoreTag } from '@features/tag/services/tag'; import { Translate } from '@singletons'; -import { CoreFileUploader } from '@features/fileuploader/services/fileuploader'; +import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader'; import { AddonModForumSync } from '../../services/forum-sync'; import { CoreSync } from '@services/sync'; import { CoreText } from '@singletons/text'; @@ -57,6 +57,7 @@ import { CoreToasts } from '@services/toasts'; import { toBoolean } from '@/core/transforms/boolean'; import { CorePopovers } from '@services/popovers'; import { CoreLoadings } from '@services/loadings'; +import { CoreWSFile } from '@services/ws'; /** * Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.). @@ -214,7 +215,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges this.formData.isEditing = !!isEditing; this.formData.subject = subject || this.defaultReplySubject || ''; this.formData.message = message || null; - this.formData.files = files || []; + this.formData.files = (files ?? []).slice(); // Make a copy to avoid modifying the original array. this.formData.isprivatereply = !!isPrivate; this.formData.id = postId; @@ -392,10 +393,9 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges return; } - let saveOffline = false; let message = this.formData.message; const subject = this.formData.subject; - const replyingTo = this.formData.replyingTo!; + const replyingTo = this.formData.replyingTo ?? 0; const files = this.formData.files || []; const isEditOnline = this.formData.id && this.formData.id > 0; const modal = await CoreLoadings.show('core.sending', true); @@ -413,73 +413,56 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges // Add some HTML to the message if needed. message = CoreText.formatHtmlLines(message); - // Upload attachments first if any. - let attachments; - try { - if (files.length) { - try { - attachments = await AddonModForumHelper.uploadOrStoreReplyFiles( - this.forum.id, - isEditOnline ? this.formData.id! : replyingTo, - files, - false, - ); - } catch (error) { - // Cannot upload them in online, save them in offline. - if (!this.forum.id || isEditOnline || CoreUtils.isWebServiceError(error)) { - // Cannot store them in offline. Reject. - throw error; - } - - saveOffline = true; - attachments = await AddonModForumHelper.uploadOrStoreReplyFiles(this.forum.id, replyingTo, files, true); - } - } - let sent = false; - if (isEditOnline) { - sent = await AddonModForum.updatePost(this.formData.id!, subject, message, { + if (this.formData.id && this.formData.id > 0) { + const attachments = await this.uploadAttachmentsForEditOnline(this.formData.id); + + sent = await AddonModForum.updatePost(this.formData.id, subject, message, { attachmentsid: attachments, inlineattachmentsid: this.preparePostData?.draftitemid, }); - } else if (saveOffline) { - // Save post in offline. - await AddonModForumOffline.replyPost( - replyingTo, - this.discussionId, - this.forum.id, - this.forum.name, - this.courseId, - subject, - message, - { - attachmentsid: attachments, - private: !!this.formData.isprivatereply, - }, - ); - - // Set sent to false since it wasn't sent to server. - sent = false; } else { - // Try to send it to server. - // Don't allow offline if there are attachments since they were uploaded fine. - sent = await AddonModForum.replyPost( - replyingTo, - this.discussionId, - this.forum.id, - this.forum.name, - this.courseId, - subject, - message, - { - attachmentsid: attachments, - private: !!this.formData.isprivatereply, - }, - undefined, - !files.length, - ); + const { attachments, saveOffline } = await this.uploadAttachmentsForReply(replyingTo); + + if (saveOffline) { + // Save post in offline. + await AddonModForumOffline.replyPost( + replyingTo, + this.discussionId, + this.forum.id, + this.forum.name, + this.courseId, + subject, + message, + { + attachmentsid: attachments, + private: !!this.formData.isprivatereply, + }, + ); + + // Set sent to false since it wasn't sent to server. + sent = false; + } else { + // Try to send it to server. + // Don't allow offline if there are attachments since they were uploaded fine. + sent = await AddonModForum.replyPost( + replyingTo, + this.discussionId, + this.forum.id, + this.forum.name, + this.courseId, + subject, + message, + { + attachmentsid: attachments, + private: !!this.formData.isprivatereply, + }, + undefined, + !files.length, + ); + } } if (sent && this.forum.id) { @@ -506,6 +489,81 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges } } + /** + * Upload attachments when editing an online post. + * + * @param postId Post ID being edited. + * @returns Draft area id (if any attachment has changed). + */ + protected async uploadAttachmentsForEditOnline(postId: number): Promise { + const files = this.formData.files || []; + const previousAttachments = (this.post.attachments ?? []) as CoreWSFile[]; + + if (!CoreFileUploader.areFileListDifferent(files, previousAttachments)) { + return; + } + + // Use prepare post for edition to avoid re-uploading all files. + let filesToKeep = files.filter((file): file is CoreWSFile => !CoreUtils.isFileEntry(file)); + let removedFiles: { filepath: string; filename: string }[] | undefined; + + if (previousAttachments.length && !filesToKeep.length) { + // Post had attachments but they were all removed. We cannot use the filesToKeep option because it doesn't allow + // removing all files. In this case we'll just keep 1 file and remove it later. + filesToKeep = [previousAttachments[0]]; + removedFiles = [{ + filename: previousAttachments[0].filename ?? '', + filepath: previousAttachments[0].filepath ?? '', + }]; + } + + const preparePostData = await AddonModForum.preparePostForEdition(postId, 'attachment', { filesToKeep }); + + if (removedFiles?.length) { + await CoreFileUploader.deleteDraftFiles(preparePostData.draftitemid, removedFiles); + } + + await CoreFileUploader.uploadFiles(preparePostData.draftitemid, files); + + return preparePostData.draftitemid; + } + + /** + * Upload attachments for a reply that isn't an online post being edited. + * + * @param replyingTo Replying to post ID. + * @returns Draft area id (if any attachment was uploaded) and whether data should be saved offline. + */ + async uploadAttachmentsForReply( + replyingTo: number, + ): Promise<{ attachments: CoreFileUploaderStoreFilesResult | number | undefined; saveOffline: boolean }> { + const files = this.formData.files || []; + if (!files.length) { + return { attachments: undefined, saveOffline: false }; + } + + try { + const attachments = await AddonModForumHelper.uploadOrStoreReplyFiles( + this.forum.id, + replyingTo, + files, + false, + ); + + return { attachments, saveOffline: false }; + } catch (error) { + // Cannot upload them in online, save them in offline. + if (!this.forum.id || CoreUtils.isWebServiceError(error)) { + // Cannot store them in offline. Reject. + throw error; + } + + const attachments = await AddonModForumHelper.uploadOrStoreReplyFiles(this.forum.id, replyingTo, files, true); + + return { attachments, saveOffline: true }; + } + } + /** * Cancel reply. */