|
@@ -1,5 +1,6 @@
|
|
|
import { randomUUID } from 'crypto';
|
|
import { randomUUID } from 'crypto';
|
|
|
import { createReadStream, ReadStream } from 'fs';
|
|
import { createReadStream, ReadStream } from 'fs';
|
|
|
|
|
+import { basename } from 'path';
|
|
|
import { Readable } from 'stream';
|
|
import { Readable } from 'stream';
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-restricted-imports
|
|
// eslint-disable-next-line no-restricted-imports
|
|
@@ -49,10 +50,13 @@ export type IDataGROWIInfo = {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Attachment data already exsisting in the new GROWI
|
|
|
|
|
|
|
+ * File metadata in storage
|
|
|
|
|
+ * TODO: mv this to "./file-uploader/uploader"
|
|
|
*/
|
|
*/
|
|
|
-// TODO: use Attachemnt model type
|
|
|
|
|
-export type Attachment = any;
|
|
|
|
|
|
|
+interface FileMeta {
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ size: number;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Return type for {@link Pusher.getTransferability}
|
|
* Return type for {@link Pusher.getTransferability}
|
|
@@ -70,11 +74,16 @@ interface Pusher {
|
|
|
* @param {IDataGROWIInfo} fromGROWIInfo
|
|
* @param {IDataGROWIInfo} fromGROWIInfo
|
|
|
*/
|
|
*/
|
|
|
getTransferability(fromGROWIInfo: IDataGROWIInfo): Promise<IGetTransferabilityReturn>
|
|
getTransferability(fromGROWIInfo: IDataGROWIInfo): Promise<IGetTransferabilityReturn>
|
|
|
|
|
+ /**
|
|
|
|
|
+ * List files in the storage
|
|
|
|
|
+ * @param {TransferKey} tk Transfer key
|
|
|
|
|
+ */
|
|
|
|
|
+ listFilesInStorage(tk: TransferKey): Promise<FileMeta[]>
|
|
|
/**
|
|
/**
|
|
|
* Transfer all Attachment data to destination GROWI
|
|
* Transfer all Attachment data to destination GROWI
|
|
|
* @param {TransferKey} tk Transfer key
|
|
* @param {TransferKey} tk Transfer key
|
|
|
*/
|
|
*/
|
|
|
- transferAttachments(tk: TransferKey, attachmentIdsFromNewGrowi: string[]): Promise<void>
|
|
|
|
|
|
|
+ transferAttachments(tk: TransferKey): Promise<void>
|
|
|
/**
|
|
/**
|
|
|
* Start transfer data between GROWIs
|
|
* Start transfer data between GROWIs
|
|
|
* @param {TransferKey} tk TransferKey object
|
|
* @param {TransferKey} tk TransferKey object
|
|
@@ -87,7 +96,6 @@ interface Pusher {
|
|
|
toGROWIInfo: IDataGROWIInfo,
|
|
toGROWIInfo: IDataGROWIInfo,
|
|
|
collections: string[],
|
|
collections: string[],
|
|
|
optionsMap: any,
|
|
optionsMap: any,
|
|
|
- attachmentIdsFromNewGrowi: string[]
|
|
|
|
|
): Promise<void>
|
|
): Promise<void>
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -225,7 +233,7 @@ export class G2GTransferPusherService implements Pusher {
|
|
|
}
|
|
}
|
|
|
catch (err) {
|
|
catch (err) {
|
|
|
logger.error(err);
|
|
logger.error(err);
|
|
|
- throw new G2GTransferError('Failed to retreive growi info.', G2GTransferErrorCode.FAILED_TO_RETREIVE_GROWI_INFO);
|
|
|
|
|
|
|
+ throw new G2GTransferError('Failed to retrieve growi info.', G2GTransferErrorCode.FAILED_TO_RETRIEVE_GROWI_INFO);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return toGROWIInfo;
|
|
return toGROWIInfo;
|
|
@@ -278,25 +286,73 @@ export class G2GTransferPusherService implements Pusher {
|
|
|
return { canTransfer: true };
|
|
return { canTransfer: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public async getAttachments(tk: TransferKey): Promise<string[]> {
|
|
|
|
|
|
|
+ public async listFilesInStorage(tk: TransferKey): Promise<FileMeta[]> {
|
|
|
try {
|
|
try {
|
|
|
- const { data } = await axios.get<string[]>('/_api/v3/g2g-transfer/attachments', generateAxiosRequestConfigWithTransferKey(tk));
|
|
|
|
|
- return data;
|
|
|
|
|
|
|
+ const { data: { files } } = await axios.get<{ files: FileMeta[] }>('/_api/v3/g2g-transfer/files', generateAxiosRequestConfigWithTransferKey(tk));
|
|
|
|
|
+ return files;
|
|
|
}
|
|
}
|
|
|
catch (err) {
|
|
catch (err) {
|
|
|
logger.error(err);
|
|
logger.error(err);
|
|
|
- throw new G2GTransferError('Failed to retreive attachments', G2GTransferErrorCode.FAILED_TO_RETREIVE_ATTACHMENTS);
|
|
|
|
|
|
|
+ throw new G2GTransferError('Failed to retrieve file metadata', G2GTransferErrorCode.FAILED_TO_RETRIEVE_FILE_METADATA);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public async transferAttachments(tk: TransferKey, attachmentIdsFromNewGrowi: string[]): Promise<void> {
|
|
|
|
|
|
|
+ public async transferAttachments(tk: TransferKey): Promise<void> {
|
|
|
const BATCH_SIZE = 100;
|
|
const BATCH_SIZE = 100;
|
|
|
-
|
|
|
|
|
const { fileUploadService } = this.crowi;
|
|
const { fileUploadService } = this.crowi;
|
|
|
const Attachment = this.crowi.model('Attachment');
|
|
const Attachment = this.crowi.model('Attachment');
|
|
|
-
|
|
|
|
|
- // batch get
|
|
|
|
|
- const attachmentsCursor = await Attachment.find({ _id: { $nin: attachmentIdsFromNewGrowi } }).cursor();
|
|
|
|
|
|
|
+ const filesFromNewGrowi = await this.listFilesInStorage(tk);
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Given these documents,
|
|
|
|
|
+ *
|
|
|
|
|
+ * | fileName | fileSize |
|
|
|
|
|
+ * | -- | -- |
|
|
|
|
|
+ * | a.png | 1024 |
|
|
|
|
|
+ * | b.png | 2048 |
|
|
|
|
|
+ * | c.png | 1024 |
|
|
|
|
|
+ * | d.png | 2048 |
|
|
|
|
|
+ *
|
|
|
|
|
+ * this filter
|
|
|
|
|
+ *
|
|
|
|
|
+ * ```jsonc
|
|
|
|
|
+ * {
|
|
|
|
|
+ * $and: [
|
|
|
|
|
+ * // a file transferred
|
|
|
|
|
+ * {
|
|
|
|
|
+ * $or: [
|
|
|
|
|
+ * { fileName: { $ne: "a.png" } },
|
|
|
|
|
+ * { fileSize: { $ne: 1024 } }
|
|
|
|
|
+ * ]
|
|
|
|
|
+ * },
|
|
|
|
|
+ * // a file failed to transfer
|
|
|
|
|
+ * {
|
|
|
|
|
+ * $or: [
|
|
|
|
|
+ * { fileName: { $ne: "b.png" } },
|
|
|
|
|
+ * { fileSize: { $ne: 0 } }
|
|
|
|
|
+ * ]
|
|
|
|
|
+ * }
|
|
|
|
|
+ * ]
|
|
|
|
|
+ * }
|
|
|
|
|
+ * ```
|
|
|
|
|
+ *
|
|
|
|
|
+ * results in
|
|
|
|
|
+ *
|
|
|
|
|
+ * | fileName | fileSize |
|
|
|
|
|
+ * | -- | -- |
|
|
|
|
|
+ * | b.png | 2048 |
|
|
|
|
|
+ * | c.png | 1024 |
|
|
|
|
|
+ * | d.png | 2048 |
|
|
|
|
|
+ */
|
|
|
|
|
+ const filter = filesFromNewGrowi.length > 0 ? {
|
|
|
|
|
+ $and: filesFromNewGrowi.map(({ name, size }) => ({
|
|
|
|
|
+ $or: [
|
|
|
|
|
+ { fileName: { $ne: basename(name) } },
|
|
|
|
|
+ { fileSize: { $ne: size } },
|
|
|
|
|
+ ],
|
|
|
|
|
+ })),
|
|
|
|
|
+ } : {};
|
|
|
|
|
+ const attachmentsCursor = await Attachment.find(filter).cursor();
|
|
|
const batchStream = createBatchStream(BATCH_SIZE);
|
|
const batchStream = createBatchStream(BATCH_SIZE);
|
|
|
|
|
|
|
|
for await (const attachmentBatch of attachmentsCursor.pipe(batchStream)) {
|
|
for await (const attachmentBatch of attachmentsCursor.pipe(batchStream)) {
|
|
@@ -325,7 +381,7 @@ export class G2GTransferPusherService implements Pusher {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// eslint-disable-next-line max-len
|
|
// eslint-disable-next-line max-len
|
|
|
- public async startTransfer(tk: TransferKey, user: any, toGROWIInfo: IDataGROWIInfo, collections: string[], optionsMap: any, attachmentIdsFromNewGrowi: string[], shouldEmit = true): Promise<void> {
|
|
|
|
|
|
|
+ public async startTransfer(tk: TransferKey, user: any, toGROWIInfo: IDataGROWIInfo, collections: string[], optionsMap: any, shouldEmit = true): Promise<void> {
|
|
|
const socket = this.crowi.socketIoService.getAdminSocket();
|
|
const socket = this.crowi.socketIoService.getAdminSocket();
|
|
|
|
|
|
|
|
if (shouldEmit) {
|
|
if (shouldEmit) {
|
|
@@ -390,7 +446,7 @@ export class G2GTransferPusherService implements Pusher {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- await this.transferAttachments(tk, attachmentIdsFromNewGrowi);
|
|
|
|
|
|
|
+ await this.transferAttachments(tk);
|
|
|
}
|
|
}
|
|
|
catch (err) {
|
|
catch (err) {
|
|
|
logger.error(err);
|
|
logger.error(err);
|