Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 38 additions & 13 deletions packages/migrate/scripts/hustoj.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,6 @@ const langMap = {
16: 'js',
17: 'go',
};
const nameMap: Record<string, string> = {
'sample.in': 'sample0.in',
'sample.out': 'sample0.out',
'test.in': 'test0.in',
'test.out': 'test0.out',
};

async function addContestFile(domainId: string, tid: ObjectId, filename: string, filepath: string) {
const tdoc = await ContestModel.get(domainId, tid);
Expand All @@ -60,6 +54,18 @@ async function addContestFile(domainId: string, tid: ObjectId, filename: string,
return true;
}

function fixFileName(fileName: string) {
if (fileName.endsWith('.in') || fileName.endsWith('.out')) {
const dotAt = fileName.lastIndexOf('.');
const name = fileName.slice(0, dotAt);
const suffix = fileName.slice(dotAt + 1);
if (!name.match(/[0-9]/)) {
fileName = `${name}0.${suffix}`;
}
}
return fileName.replace(/[\\/?#~!|*]/g, '_');
}
Comment on lines +57 to +67

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance special character replacement for better cross-platform compatibility.

The current regex pattern might miss some problematic characters that could cause issues on different platforms. Consider expanding it to include Windows reserved characters and other potentially problematic ones.

 function fixFileName(fileName: string) {
+    if (!fileName) return fileName;
     if (fileName.endsWith('.in') || fileName.endsWith('.out')) {
         const dotAt = fileName.lastIndexOf('.');
         const name = fileName.slice(0, dotAt);
         const suffix = fileName.slice(dotAt + 1);
         if (!name.match(/[0-9]/)) {
             fileName = `${name}0.${suffix}`;
         }
     }
-    return fileName.replace(/[\\/?#~!|*]/g, '_');
+    // Replace Windows reserved characters and other problematic ones
+    return fileName.replace(/[<>:"/\\|?*#~!]/g, '_');
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function fixFileName(fileName: string) {
if (fileName.endsWith('.in') || fileName.endsWith('.out')) {
const dotAt = fileName.lastIndexOf('.');
const name = fileName.slice(0, dotAt);
const suffix = fileName.slice(dotAt + 1);
if (!name.match(/[0-9]/)) {
fileName = `${name}0.${suffix}`;
}
}
return fileName.replace(/[\\/?#~!|*]/g, '_');
}
function fixFileName(fileName: string) {
if (!fileName) return fileName;
if (fileName.endsWith('.in') || fileName.endsWith('.out')) {
const dotAt = fileName.lastIndexOf('.');
const name = fileName.slice(0, dotAt);
const suffix = fileName.slice(dotAt + 1);
if (!name.match(/[0-9]/)) {
fileName = `${name}0.${suffix}`;
}
}
// Replace Windows reserved characters and other problematic ones
return fileName.replace(/[<>:"/\\|?*#~!]/g, '_');
}
🤖 Prompt for AI Agents
In packages/migrate/scripts/hustoj.ts around lines 58 to 68, the regex used to
replace special characters in file names is too limited and may miss characters
that cause issues on some platforms. Update the regex pattern in the replace
method to include a broader set of reserved and problematic characters, such as
Windows reserved characters like < > : " / \ | ? * and others, to ensure better
cross-platform compatibility.


export async function run({
host = 'localhost', port = 3306, name = 'jol',
username, password, domainId, contestType = 'oi',
Expand Down Expand Up @@ -186,11 +192,13 @@ export async function run({
hint: pdoc.hint,
source: pdoc.source,
}, 'html').replace(/<math xm<x>lns=/g, '<math xmlns=').replace(/\[\/?md]/g, '');
const uploadFiles = content.matchAll(/(?:src|href)="\/upload\/([^"]+\/([^"]+))"/g);
const uploadFiles = content.matchAll(/(?:src|href)="\/upload\/([^"/]+)(?:\/([^"/]+))?\/([^"/]+\.[^"/.]+)"/g);
for (const file of uploadFiles) {
try {
files[file[2]] = await fs.readFile(path.join(uploadDir, file[1]));
content = content.replace(`/upload/${file[1]}`, `file://${file[2]}`);
const filename = fixFileName(file[3]);
const fileWithPath = [file[1], ...(file[2] ? [file[2]] : []), file[3]].join('/');
files[filename] = await fs.readFile(path.join(uploadDir, fileWithPath));
content = content.replace(`/upload/${fileWithPath}`, `file://${filename}`);
} catch (e) {
report({ message: `failed to read file: ${path.join(uploadDir, file[1])}` });
}
Expand Down Expand Up @@ -265,13 +273,19 @@ hydrooj install https://hydro.ac/hydroac-client.zip
const pids = pdocs.map((i) => pidMap[i.problem_id]).filter((i) => i);
const files = {};
let description = tdoc.description;
const uploadFiles = description.matchAll(/(?:src|href)="\/upload\/([^"]+\/([^"]+))"/g);
const uploadFiles = description.matchAll(/(?:src|href)="\/upload\/([^"/]+)(?:\/([^"/]+))?\/([^"/]+\.[^"/.]+)"/g);
for (const file of uploadFiles) {
files[file[2]] = await fs.readFile(path.join(uploadDir, file[1]));
description = description.replace(`/upload/${file[1]}`, `file://${file[2]}`);
const filename = fixFileName(file[3]);
const fileWithPath = [file[1], ...(file[2] ? [file[2]] : []), file[3]].join('/');
files[filename] = await fs.readFile(path.join(uploadDir, fileWithPath));
description = description.replace(`/upload/${fileWithPath}`, `file://${filename}`);
}
// WHY you allow contest with end time BEFORE start time? WHY???
const endAt = moment(tdoc.end_time).isSameOrBefore(tdoc.start_time) ? moment(tdoc.end_time).add(1, 'minute').toDate() : tdoc.end_time;
let isAssignMode = false;
if (tdoc.private === 1 && tdoc.password === '') {
Comment thread
bhscer marked this conversation as resolved.
isAssignMode = true;
}
const tid = await ContestModel.add(
domainId, tdoc.title, description || 'Description',
adminUids[0], contestType, tdoc.start_time, endAt, pids, true,
Expand All @@ -281,6 +295,17 @@ hydrooj install https://hydro.ac/hydroac-client.zip
await Promise.all(Object.keys(files).map((filename) => addContestFile(domainId, tid, filename, files[filename])));
if (Object.keys(files).length) report({ message: `move ${Object.keys(files).length} file for contest ${tidMap[tdoc.contest_id]}` });

const allowedUser:{ user_id:string }[] = await query(`SELECT * FROM privilege WHERE rightstr = 'c${tdoc.contest_id}';`);
const assignUserList = allowedUser.map((i) => uidMap[i.user_id]).filter((i) => i);
if (isAssignMode) {
await ContestModel.edit(domainId, tid, {
assign: assignUserList.map((uid) => uid.toString()),
});
} else {
for (let i = 0; i < assignUserList.length; i++) {
await ContestModel.attend(domainId, tid, assignUserList[i]).catch(noop);
}
}
if (tidx % 100 === 0) {
const progress = Math.round(((tidx + 1) / tdocs.length) * 100);
report({
Expand Down Expand Up @@ -374,7 +399,7 @@ hydrooj install https://hydro.ac/hydroac-client.zip
report({ message: `Syncing testdata for ${file.name}` });
for (const data of datas) {
if (data.isDirectory()) continue;
const filename = nameMap[data.name] || data.name;
const filename = fixFileName(data.name);
await ProblemModel.addTestdata(domainId, pdoc.docId, filename, `${dataDir}/${file.name}/${data.name}`);
}
await ProblemModel.addTestdata(domainId, pdoc.docId, 'config.yaml', Buffer.from(pdoc.config as string));
Expand Down
Loading