|
@@ -1,16 +1,16 @@
|
|
|
import fs from 'fs';
|
|
import fs from 'fs';
|
|
|
import type { Readable } from 'stream';
|
|
import type { Readable } from 'stream';
|
|
|
|
|
|
|
|
-import type { IPage, IUser } from '@growi/core';
|
|
|
|
|
|
|
+import type { IUser } from '@growi/core';
|
|
|
import { isPopulated, getIdForRef } from '@growi/core';
|
|
import { isPopulated, getIdForRef } from '@growi/core';
|
|
|
import mongoose from 'mongoose';
|
|
import mongoose from 'mongoose';
|
|
|
|
|
|
|
|
|
|
|
|
|
import type { SupportedActionType } from '~/interfaces/activity';
|
|
import type { SupportedActionType } from '~/interfaces/activity';
|
|
|
import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
|
|
import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
|
|
|
|
|
+import type Crowi from '~/server/crowi';
|
|
|
import type { ObjectIdLike } from '~/server/interfaces/mongoose-utils';
|
|
import type { ObjectIdLike } from '~/server/interfaces/mongoose-utils';
|
|
|
import type { ActivityDocument } from '~/server/models/activity';
|
|
import type { ActivityDocument } from '~/server/models/activity';
|
|
|
-import type { PageModel } from '~/server/models/page';
|
|
|
|
|
import { configManager } from '~/server/service/config-manager';
|
|
import { configManager } from '~/server/service/config-manager';
|
|
|
import CronService from '~/server/service/cron';
|
|
import CronService from '~/server/service/cron';
|
|
|
import type { FileUploader } from '~/server/service/file-uploader';
|
|
import type { FileUploader } from '~/server/service/file-uploader';
|
|
@@ -32,8 +32,7 @@ import { exportPagesToFsAsync } from './steps/export-pages-to-fs-async';
|
|
|
const logger = loggerFactory('growi:service:page-bulk-export-job-cron');
|
|
const logger = loggerFactory('growi:service:page-bulk-export-job-cron');
|
|
|
|
|
|
|
|
export interface IPageBulkExportJobCronService {
|
|
export interface IPageBulkExportJobCronService {
|
|
|
- crowi: any;
|
|
|
|
|
- pageModel: PageModel;
|
|
|
|
|
|
|
+ crowi: Crowi;
|
|
|
pageBatchSize: number;
|
|
pageBatchSize: number;
|
|
|
maxPartSize: number;
|
|
maxPartSize: number;
|
|
|
compressExtension: string;
|
|
compressExtension: string;
|
|
@@ -43,9 +42,13 @@ export interface IPageBulkExportJobCronService {
|
|
|
getTmpOutputDir(pageBulkExportJob: PageBulkExportJobDocument): string;
|
|
getTmpOutputDir(pageBulkExportJob: PageBulkExportJobDocument): string;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Manages cronjob which proceeds PageBulkExportJobs in progress.
|
|
|
|
|
+ * If PageBulkExportJob finishes the current step, the next step will be started on the next cron execution.
|
|
|
|
|
+ */
|
|
|
class PageBulkExportJobCronService extends CronService implements IPageBulkExportJobCronService {
|
|
class PageBulkExportJobCronService extends CronService implements IPageBulkExportJobCronService {
|
|
|
|
|
|
|
|
- crowi: any;
|
|
|
|
|
|
|
+ crowi: Crowi;
|
|
|
|
|
|
|
|
activityEvent: any;
|
|
activityEvent: any;
|
|
|
|
|
|
|
@@ -60,22 +63,18 @@ class PageBulkExportJobCronService extends CronService implements IPageBulkExpor
|
|
|
// TODO: If necessary, change to a proper path in https://redmine.weseek.co.jp/issues/149512
|
|
// TODO: If necessary, change to a proper path in https://redmine.weseek.co.jp/issues/149512
|
|
|
tmpOutputRootDir = '/tmp/page-bulk-export';
|
|
tmpOutputRootDir = '/tmp/page-bulk-export';
|
|
|
|
|
|
|
|
- pageModel: PageModel;
|
|
|
|
|
-
|
|
|
|
|
- userModel: mongoose.Model<IUser>;
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // Keep track of the stream executed for PageBulkExportJob to destroy it on job failure.
|
|
|
|
|
+ // The key is the id of a PageBulkExportJob.
|
|
|
private streamInExecutionMemo: {
|
|
private streamInExecutionMemo: {
|
|
|
[key: string]: Readable;
|
|
[key: string]: Readable;
|
|
|
} = {};
|
|
} = {};
|
|
|
|
|
|
|
|
private parallelExecLimit: number;
|
|
private parallelExecLimit: number;
|
|
|
|
|
|
|
|
- constructor(crowi) {
|
|
|
|
|
|
|
+ constructor(crowi: Crowi) {
|
|
|
super();
|
|
super();
|
|
|
this.crowi = crowi;
|
|
this.crowi = crowi;
|
|
|
this.activityEvent = crowi.event('activity');
|
|
this.activityEvent = crowi.event('activity');
|
|
|
- this.pageModel = mongoose.model<IPage, PageModel>('Page');
|
|
|
|
|
- this.userModel = mongoose.model<IUser>('User');
|
|
|
|
|
this.parallelExecLimit = configManager.getConfig('crowi', 'app:pageBulkExportParallelExecLimit');
|
|
this.parallelExecLimit = configManager.getConfig('crowi', 'app:pageBulkExportParallelExecLimit');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -91,6 +90,10 @@ class PageBulkExportJobCronService extends CronService implements IPageBulkExpor
|
|
|
pageBulkExportJobsInProgress.forEach((pageBulkExportJob) => {
|
|
pageBulkExportJobsInProgress.forEach((pageBulkExportJob) => {
|
|
|
this.proceedBulkExportJob(pageBulkExportJob);
|
|
this.proceedBulkExportJob(pageBulkExportJob);
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+ if (pageBulkExportJobsInProgress.length === 0) {
|
|
|
|
|
+ this.stopCron();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -101,21 +104,31 @@ class PageBulkExportJobCronService extends CronService implements IPageBulkExpor
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Get the stream in execution of a job.
|
|
|
|
|
|
|
+ * Get the stream in execution for a job.
|
|
|
* A getter method that includes "undefined" in the return type
|
|
* A getter method that includes "undefined" in the return type
|
|
|
*/
|
|
*/
|
|
|
getStreamInExecution(jobId: ObjectIdLike): Readable | undefined {
|
|
getStreamInExecution(jobId: ObjectIdLike): Readable | undefined {
|
|
|
return this.streamInExecutionMemo[jobId.toString()];
|
|
return this.streamInExecutionMemo[jobId.toString()];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Set the stream in execution for a job
|
|
|
|
|
+ */
|
|
|
setStreamInExecution(jobId: ObjectIdLike, stream: Readable) {
|
|
setStreamInExecution(jobId: ObjectIdLike, stream: Readable) {
|
|
|
this.streamInExecutionMemo[jobId.toString()] = stream;
|
|
this.streamInExecutionMemo[jobId.toString()] = stream;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Remove the stream in execution for a job
|
|
|
|
|
+ */
|
|
|
removeStreamInExecution(jobId: ObjectIdLike) {
|
|
removeStreamInExecution(jobId: ObjectIdLike) {
|
|
|
delete this.streamInExecutionMemo[jobId.toString()];
|
|
delete this.streamInExecutionMemo[jobId.toString()];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Proceed the page bulk export job if the next step is executable
|
|
|
|
|
+ * @param pageBulkExportJob PageBulkExportJob in progress
|
|
|
|
|
+ */
|
|
|
async proceedBulkExportJob(pageBulkExportJob: PageBulkExportJobDocument) {
|
|
async proceedBulkExportJob(pageBulkExportJob: PageBulkExportJobDocument) {
|
|
|
if (pageBulkExportJob.restartFlag) {
|
|
if (pageBulkExportJob.restartFlag) {
|
|
|
await this.cleanUpExportJobResources(pageBulkExportJob, true);
|
|
await this.cleanUpExportJobResources(pageBulkExportJob, true);
|
|
@@ -125,11 +138,13 @@ class PageBulkExportJobCronService extends CronService implements IPageBulkExpor
|
|
|
await pageBulkExportJob.save();
|
|
await pageBulkExportJob.save();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // return if job is still the same status as the previous cron exec
|
|
|
if (pageBulkExportJob.status === pageBulkExportJob.statusOnPreviousCronExec) {
|
|
if (pageBulkExportJob.status === pageBulkExportJob.statusOnPreviousCronExec) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
+ const User = mongoose.model<IUser>('User');
|
|
|
try {
|
|
try {
|
|
|
- const user = await this.userModel.findById(getIdForRef(pageBulkExportJob.user));
|
|
|
|
|
|
|
+ const user = await User.findById(getIdForRef(pageBulkExportJob.user));
|
|
|
|
|
|
|
|
// update statusOnPreviousCronExec before starting processes that updates status
|
|
// update statusOnPreviousCronExec before starting processes that updates status
|
|
|
pageBulkExportJob.statusOnPreviousCronExec = pageBulkExportJob.status;
|
|
pageBulkExportJob.statusOnPreviousCronExec = pageBulkExportJob.status;
|
|
@@ -151,6 +166,11 @@ class PageBulkExportJobCronService extends CronService implements IPageBulkExpor
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Handle errors that occurred inside a stream pipeline
|
|
|
|
|
+ * @param err error
|
|
|
|
|
+ * @param pageBulkExportJob PageBulkExportJob executed in the pipeline
|
|
|
|
|
+ */
|
|
|
async handlePipelineError(err: Error | null, pageBulkExportJob: PageBulkExportJobDocument) {
|
|
async handlePipelineError(err: Error | null, pageBulkExportJob: PageBulkExportJobDocument) {
|
|
|
if (err == null) return;
|
|
if (err == null) return;
|
|
|
|
|
|
|
@@ -246,6 +266,6 @@ class PageBulkExportJobCronService extends CronService implements IPageBulkExpor
|
|
|
|
|
|
|
|
// eslint-disable-next-line import/no-mutable-exports
|
|
// eslint-disable-next-line import/no-mutable-exports
|
|
|
export let pageBulkExportJobCronService: PageBulkExportJobCronService | undefined; // singleton instance
|
|
export let pageBulkExportJobCronService: PageBulkExportJobCronService | undefined; // singleton instance
|
|
|
-export default function instanciate(crowi): void {
|
|
|
|
|
|
|
+export default function instanciate(crowi: Crowi): void {
|
|
|
pageBulkExportJobCronService = new PageBulkExportJobCronService(crowi);
|
|
pageBulkExportJobCronService = new PageBulkExportJobCronService(crowi);
|
|
|
}
|
|
}
|