This commit addresses issues #264 and #304, where users were not receiving error messages when script execution failed due to antivirus intervention, particularly with Microsoft Defender. Now, desktop app users will see a detailed error message with guidance on next steps if script saving or execution fails due to antivirus removal. Key changes: - Implement a check to detect failure in file writing, including reading the written file back. This method effectively detects antivirus interventions, as the read operation triggers an antivirus scan, leading to file deletion by the antivirus. - Introduce a specific error message for scenarios where an antivirus intervention is detected.
123 lines
4.2 KiB
TypeScript
123 lines
4.2 KiB
TypeScript
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
|
import { Logger } from '@/application/Common/Log/Logger';
|
|
import { CodeRunError, CodeRunErrorType } from '@/application/CodeRunner/CodeRunner';
|
|
import { FileReadbackVerificationErrors, ReadbackFileWriter } from '@/infrastructure/ReadbackFileWriter/ReadbackFileWriter';
|
|
import { NodeReadbackFileWriter } from '@/infrastructure/ReadbackFileWriter/NodeReadbackFileWriter';
|
|
import { SystemOperations } from '../System/SystemOperations';
|
|
import { NodeElectronSystemOperations } from '../System/NodeElectronSystemOperations';
|
|
import { FilenameGenerator } from './Filename/FilenameGenerator';
|
|
import { ScriptFilenameParts, ScriptFileCreator, ScriptFileCreationOutcome } from './ScriptFileCreator';
|
|
import { TimestampedFilenameGenerator } from './Filename/TimestampedFilenameGenerator';
|
|
import { ScriptDirectoryProvider } from './Directory/ScriptDirectoryProvider';
|
|
import { PersistentDirectoryProvider } from './Directory/PersistentDirectoryProvider';
|
|
|
|
export class ScriptFileCreationOrchestrator implements ScriptFileCreator {
|
|
constructor(
|
|
private readonly system: SystemOperations = new NodeElectronSystemOperations(),
|
|
private readonly filenameGenerator: FilenameGenerator = new TimestampedFilenameGenerator(),
|
|
private readonly directoryProvider: ScriptDirectoryProvider = new PersistentDirectoryProvider(),
|
|
private readonly fileWriter: ReadbackFileWriter = new NodeReadbackFileWriter(),
|
|
private readonly logger: Logger = ElectronLogger,
|
|
) { }
|
|
|
|
public async createScriptFile(
|
|
contents: string,
|
|
scriptFilenameParts: ScriptFilenameParts,
|
|
): Promise<ScriptFileCreationOutcome> {
|
|
const {
|
|
success: isDirectoryCreated, error: directoryCreationError, directoryAbsolutePath,
|
|
} = await this.directoryProvider.provideScriptDirectory();
|
|
if (!isDirectoryCreated) {
|
|
return createFailure(directoryCreationError);
|
|
}
|
|
const {
|
|
success: isFilePathConstructed, error: filePathGenerationError, filePath,
|
|
} = this.constructFilePath(scriptFilenameParts, directoryAbsolutePath);
|
|
if (!isFilePathConstructed) {
|
|
return createFailure(filePathGenerationError);
|
|
}
|
|
const {
|
|
success: isFileCreated, error: fileCreationError,
|
|
} = await this.writeFile(filePath, contents);
|
|
if (!isFileCreated) {
|
|
return createFailure(fileCreationError);
|
|
}
|
|
return {
|
|
success: true,
|
|
scriptFileAbsolutePath: filePath,
|
|
};
|
|
}
|
|
|
|
private constructFilePath(
|
|
scriptFilenameParts: ScriptFilenameParts,
|
|
directoryPath: string,
|
|
): FilePathConstructionOutcome {
|
|
try {
|
|
const filename = this.filenameGenerator.generateFilename(scriptFilenameParts);
|
|
const filePath = this.system.location.combinePaths(directoryPath, filename);
|
|
return { success: true, filePath };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: this.handleException(error, 'FilePathGenerationError'),
|
|
};
|
|
}
|
|
}
|
|
|
|
private async writeFile(
|
|
filePath: string,
|
|
contents: string,
|
|
): Promise<FileWriteOutcome> {
|
|
const {
|
|
success, error,
|
|
} = await this.fileWriter.writeAndVerifyFile(filePath, contents);
|
|
if (success) {
|
|
return { success: true };
|
|
}
|
|
return {
|
|
success: false,
|
|
error: {
|
|
message: error.message,
|
|
type: FileReadbackVerificationErrors.find((e) => e === error.type) ? 'FileReadbackVerificationError' : 'FileWriteError',
|
|
},
|
|
};
|
|
}
|
|
|
|
private handleException(
|
|
exception: Error,
|
|
errorType: CodeRunErrorType,
|
|
): CodeRunError {
|
|
const errorMessage = 'Error during script file operation';
|
|
this.logger.error(errorType, errorMessage, exception);
|
|
return {
|
|
type: errorType,
|
|
message: `${errorMessage}: ${exception.message}`,
|
|
};
|
|
}
|
|
}
|
|
|
|
function createFailure(error: CodeRunError): ScriptFileCreationOutcome {
|
|
return {
|
|
success: false,
|
|
error,
|
|
};
|
|
}
|
|
|
|
type FileWriteOutcome = {
|
|
readonly success: true;
|
|
readonly error?: undefined;
|
|
} | {
|
|
readonly success: false;
|
|
readonly error: CodeRunError;
|
|
};
|
|
|
|
type FilePathConstructionOutcome = {
|
|
readonly success: true;
|
|
readonly filePath: string;
|
|
readonly error?: undefined;
|
|
} | {
|
|
readonly success: false;
|
|
readonly filePath?: undefined;
|
|
readonly error: CodeRunError;
|
|
};
|