Files
cronmaster/app/_utils/line-manipulation-utils.ts

604 lines
14 KiB
TypeScript

import { CronJob } from "@/app/_utils/cronjob-utils";
import { generateShortUUID } from "@/app/_utils/uuid-utils";
import { createHash } from "crypto";
const generateStableJobId = (
schedule: string,
command: string,
user: string,
comment?: string,
lineIndex?: number
): string => {
const content = `${schedule}|${command}|${user}|${comment || ""}|${
lineIndex || 0
}`;
const hash = createHash("md5").update(content).digest("hex");
return hash.substring(0, 8);
};
export const pauseJobInLines = (
lines: string[],
targetJobIndex: number,
uuid: string
): string[] => {
const newCronEntries: string[] = [];
let currentJobIndex = 0;
let i = 0;
while (i < lines.length) {
const line = lines[i];
const trimmedLine = line.trim();
if (!trimmedLine) {
if (
newCronEntries.length > 0 &&
newCronEntries[newCronEntries.length - 1] !== ""
) {
newCronEntries.push("");
}
i++;
continue;
}
if (
trimmedLine.startsWith("# User:") ||
trimmedLine.startsWith("# System Crontab")
) {
newCronEntries.push(line);
i++;
continue;
}
if (trimmedLine.startsWith("# PAUSED:")) {
newCronEntries.push(line);
if (i + 1 < lines.length && lines[i + 1].trim().startsWith("# ")) {
newCronEntries.push(lines[i + 1]);
i += 2;
} else {
i++;
}
currentJobIndex++;
continue;
}
if (trimmedLine.startsWith("#")) {
if (
i + 1 < lines.length &&
!lines[i + 1].trim().startsWith("#") &&
lines[i + 1].trim()
) {
if (currentJobIndex === targetJobIndex) {
const commentText = trimmedLine.substring(1).trim();
const { comment, logsEnabled } = parseCommentMetadata(commentText);
const formattedComment = formatCommentWithMetadata(
comment,
logsEnabled,
uuid
);
const nextLine = lines[i + 1].trim();
const pausedEntry = `# PAUSED: ${formattedComment}\n# ${nextLine}`;
newCronEntries.push(pausedEntry);
i += 2;
currentJobIndex++;
} else {
newCronEntries.push(line);
newCronEntries.push(lines[i + 1]);
i += 2;
currentJobIndex++;
}
} else {
newCronEntries.push(line);
i++;
}
continue;
}
if (currentJobIndex === targetJobIndex) {
const formattedComment = formatCommentWithMetadata("", false, uuid);
const pausedEntry = `# PAUSED: ${formattedComment}\n# ${trimmedLine}`;
newCronEntries.push(pausedEntry);
} else {
newCronEntries.push(line);
}
currentJobIndex++;
i++;
}
return newCronEntries;
};
export const resumeJobInLines = (
lines: string[],
targetJobIndex: number,
uuid: string
): string[] => {
const newCronEntries: string[] = [];
let currentJobIndex = 0;
let i = 0;
while (i < lines.length) {
const line = lines[i];
const trimmedLine = line.trim();
if (!trimmedLine) {
if (
newCronEntries.length > 0 &&
newCronEntries[newCronEntries.length - 1] !== ""
) {
newCronEntries.push("");
}
i++;
continue;
}
if (
trimmedLine.startsWith("# User:") ||
trimmedLine.startsWith("# System Crontab")
) {
newCronEntries.push(line);
i++;
continue;
}
if (trimmedLine.startsWith("# PAUSED:")) {
if (currentJobIndex === targetJobIndex) {
const commentText = trimmedLine.substring(9).trim();
const { comment, logsEnabled } = parseCommentMetadata(commentText);
if (i + 1 < lines.length && lines[i + 1].trim().startsWith("# ")) {
const cronLine = lines[i + 1].trim().substring(2);
const formattedComment = formatCommentWithMetadata(
comment,
logsEnabled,
uuid
);
const resumedEntry = formattedComment
? `# ${formattedComment}\n${cronLine}`
: cronLine;
newCronEntries.push(resumedEntry);
i += 2;
} else {
i++;
}
} else {
newCronEntries.push(line);
if (i + 1 < lines.length && lines[i + 1].trim().startsWith("# ")) {
newCronEntries.push(lines[i + 1]);
i += 2;
} else {
i++;
}
}
currentJobIndex++;
continue;
}
if (trimmedLine.startsWith("#")) {
newCronEntries.push(line);
i++;
continue;
}
newCronEntries.push(line);
currentJobIndex++;
i++;
}
return newCronEntries;
};
export const parseCommentMetadata = (
commentText: string
): { comment: string; logsEnabled: boolean; uuid?: string } => {
if (!commentText) {
return { comment: "", logsEnabled: false };
}
const parts = commentText.split("|").map((p) => p.trim());
let comment = "";
let logsEnabled = false;
let uuid: string | undefined;
if (parts.length > 1) {
const firstPartIsMetadata =
parts[0].match(/logsEnabled:\s*(true|false)/i) ||
parts[0].match(/id:\s*([a-z0-9]{8}|[a-z0-9]{4}-[a-z0-9]{4})/i);
if (firstPartIsMetadata) {
comment = "";
const metadata = parts.join("|").trim();
const logsMatch = metadata.match(/logsEnabled:\s*(true|false)/i);
if (logsMatch) {
logsEnabled = logsMatch[1].toLowerCase() === "true";
}
const uuidMatches = Array.from(
metadata.matchAll(/id:\s*([a-z0-9]{8}|[a-z0-9]{4}-[a-z0-9]{4})/gi)
);
if (uuidMatches.length > 0) {
uuid = uuidMatches[uuidMatches.length - 1][1].toLowerCase();
}
} else {
comment = parts[0] || "";
const metadata = parts.slice(1).join("|").trim();
const logsMatch = metadata.match(/logsEnabled:\s*(true|false)/i);
if (logsMatch) {
logsEnabled = logsMatch[1].toLowerCase() === "true";
}
const uuidMatches = Array.from(
metadata.matchAll(/id:\s*([a-z0-9]{8}|[a-z0-9]{4}-[a-z0-9]{4})/gi)
);
if (uuidMatches.length > 0) {
uuid = uuidMatches[uuidMatches.length - 1][1].toLowerCase();
}
}
} else {
const logsMatch = commentText.match(/logsEnabled:\s*(true|false)/i);
const uuidMatch = commentText.match(
/id:\s*([a-z0-9]{8}|[a-z0-9]{4}-[a-z0-9]{4})/i
);
if (logsMatch || uuidMatch) {
if (logsMatch) {
logsEnabled = logsMatch[1].toLowerCase() === "true";
}
if (uuidMatch) {
uuid = uuidMatch[1].toLowerCase();
}
comment = "";
} else {
comment = parts[0] || "";
}
}
return { comment, logsEnabled, uuid };
};
export const formatCommentWithMetadata = (
comment: string,
logsEnabled: boolean,
uuid: string
): string => {
const trimmedComment = comment.trim();
const metadataParts: string[] = [];
if (logsEnabled) {
metadataParts.push("logsEnabled: true");
}
metadataParts.push(`id: ${uuid}`);
const metadata = metadataParts.join(" | ");
if (trimmedComment) {
return `${trimmedComment} | ${metadata}`;
}
return metadata;
};
export const parseJobsFromLines = (
lines: string[],
user: string
): CronJob[] => {
const jobs: CronJob[] = [];
let currentComment = "";
let currentLogsEnabled = false;
let currentUuid: string | undefined;
let jobIndex = 0;
let i = 0;
while (i < lines.length) {
const line = lines[i];
const trimmedLine = line.trim();
if (!trimmedLine) {
i++;
continue;
}
if (
trimmedLine.startsWith("# User:") ||
trimmedLine.startsWith("# System Crontab")
) {
i++;
continue;
}
if (trimmedLine.startsWith("# PAUSED:")) {
const commentText = trimmedLine.substring(9).trim();
const { comment, logsEnabled, uuid } = parseCommentMetadata(commentText);
if (i + 1 < lines.length) {
const nextLine = lines[i + 1].trim();
if (nextLine.startsWith("# ")) {
const commentedCron = nextLine.substring(2);
const parts = commentedCron.split(/\s+/);
if (parts.length >= 6) {
const schedule = parts.slice(0, 5).join(" ");
const command = parts.slice(5).join(" ");
const jobId =
uuid || generateStableJobId(schedule, command, user, comment, i);
jobs.push({
id: jobId,
schedule,
command,
comment: comment || undefined,
user,
paused: true,
logsEnabled,
});
jobIndex++;
i += 2;
continue;
}
}
}
i++;
continue;
}
if (trimmedLine.startsWith("#")) {
if (
i + 1 < lines.length &&
!lines[i + 1].trim().startsWith("#") &&
lines[i + 1].trim()
) {
const commentText = trimmedLine.substring(1).trim();
const { comment, logsEnabled, uuid } =
parseCommentMetadata(commentText);
currentComment = comment;
currentLogsEnabled = logsEnabled;
currentUuid = uuid;
i++;
continue;
} else {
i++;
continue;
}
}
let schedule, command;
const parts = trimmedLine.split(/(?:\s|\t)+/);
if (parts[0].startsWith("@")) {
if (parts.length >= 2) {
schedule = parts[0];
command = trimmedLine.slice(trimmedLine.indexOf(parts[1]));
}
} else if (parts.length >= 6) {
schedule = parts.slice(0, 5).join(" ");
command = trimmedLine.slice(trimmedLine.indexOf(parts[5]));
}
if (schedule && command) {
const jobId =
currentUuid ||
generateStableJobId(schedule, command, user, currentComment, i);
jobs.push({
id: jobId,
schedule,
command,
comment: currentComment || undefined,
user,
paused: false,
logsEnabled: currentLogsEnabled,
});
jobIndex++;
currentComment = "";
currentLogsEnabled = false;
currentUuid = undefined;
}
i++;
}
return jobs;
};
export const deleteJobInLines = (
lines: string[],
targetJobIndex: number
): string[] => {
const newCronEntries: string[] = [];
let currentJobIndex = 0;
let i = 0;
while (i < lines.length) {
const line = lines[i];
const trimmedLine = line.trim();
if (!trimmedLine) {
if (
newCronEntries.length > 0 &&
newCronEntries[newCronEntries.length - 1] !== ""
) {
newCronEntries.push("");
}
i++;
continue;
}
if (
trimmedLine.startsWith("# User:") ||
trimmedLine.startsWith("# System Crontab")
) {
newCronEntries.push(line);
i++;
continue;
}
if (trimmedLine.startsWith("# PAUSED:")) {
if (currentJobIndex !== targetJobIndex) {
newCronEntries.push(line);
if (i + 1 < lines.length && lines[i + 1].trim().startsWith("# ")) {
newCronEntries.push(lines[i + 1]);
i += 2;
} else {
i++;
}
} else {
if (i + 1 < lines.length && lines[i + 1].trim().startsWith("# ")) {
i += 2;
} else {
i++;
}
}
currentJobIndex++;
continue;
}
if (trimmedLine.startsWith("#")) {
if (
i + 1 < lines.length &&
!lines[i + 1].trim().startsWith("#") &&
lines[i + 1].trim()
) {
if (currentJobIndex !== targetJobIndex) {
newCronEntries.push(line);
newCronEntries.push(lines[i + 1]);
}
i += 2;
currentJobIndex++;
} else {
newCronEntries.push(line);
i++;
}
continue;
}
if (currentJobIndex !== targetJobIndex) {
newCronEntries.push(line);
}
currentJobIndex++;
i++;
}
return newCronEntries;
};
export const updateJobInLines = (
lines: string[],
targetJobIndex: number,
schedule: string,
command: string,
comment: string = "",
logsEnabled: boolean = false,
uuid: string
): string[] => {
const newCronEntries: string[] = [];
let currentJobIndex = 0;
let i = 0;
while (i < lines.length) {
const line = lines[i];
const trimmedLine = line.trim();
if (!trimmedLine) {
if (
newCronEntries.length > 0 &&
newCronEntries[newCronEntries.length - 1] !== ""
) {
newCronEntries.push("");
}
i++;
continue;
}
if (
trimmedLine.startsWith("# User:") ||
trimmedLine.startsWith("# System Crontab")
) {
newCronEntries.push(line);
i++;
continue;
}
if (trimmedLine.startsWith("# PAUSED:")) {
if (currentJobIndex === targetJobIndex) {
const formattedComment = formatCommentWithMetadata(
comment,
logsEnabled,
uuid
);
const newEntry = formattedComment
? `# PAUSED: ${formattedComment}\n# ${schedule} ${command}`
: `# PAUSED:\n# ${schedule} ${command}`;
newCronEntries.push(newEntry);
if (i + 1 < lines.length && lines[i + 1].trim().startsWith("# ")) {
i += 2;
} else {
i++;
}
} else {
newCronEntries.push(line);
if (i + 1 < lines.length && lines[i + 1].trim().startsWith("# ")) {
newCronEntries.push(lines[i + 1]);
i += 2;
} else {
i++;
}
}
currentJobIndex++;
continue;
}
if (trimmedLine.startsWith("#")) {
if (
i + 1 < lines.length &&
!lines[i + 1].trim().startsWith("#") &&
lines[i + 1].trim()
) {
if (currentJobIndex === targetJobIndex) {
const formattedComment = formatCommentWithMetadata(
comment,
logsEnabled,
uuid
);
const newEntry = formattedComment
? `# ${formattedComment}\n${schedule} ${command}`
: `${schedule} ${command}`;
newCronEntries.push(newEntry);
i += 2;
} else {
newCronEntries.push(line);
newCronEntries.push(lines[i + 1]);
i += 2;
}
currentJobIndex++;
} else {
newCronEntries.push(line);
i++;
}
continue;
}
if (currentJobIndex === targetJobIndex) {
const formattedComment = formatCommentWithMetadata(
comment,
logsEnabled,
uuid
);
const newEntry = formattedComment
? `# ${formattedComment}\n${schedule} ${command}`
: `${schedule} ${command}`;
newCronEntries.push(newEntry);
} else {
newCronEntries.push(line);
}
currentJobIndex++;
i++;
}
return newCronEntries;
};