/ *
* This file is part of SYZOJ .
*
* Copyright ( c ) 2016 Menci < huanghaorui301 @ gmail . com >
*
* SYZOJ is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* SYZOJ is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public
* License along with SYZOJ . If not , see < http : //www.gnu.org/licenses/>.
* /
'use strict' ;
let statisticsStatements = {
fastest :
' \
SELECT \
DISTINCT ( ` user_id ` ) AS ` user_id ` , \
( \
SELECT \
` id ` \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` total_time ` ASC \
LIMIT 1 \
) AS ` id ` , \
( \
SELECT \
` total_time ` \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` total_time ` ASC \
LIMIT 1 \
) AS ` total_time ` \
FROM ` judge_state ` ` outer_table ` \
WHERE \
` problem_id ` = _ _PROBLEM _ID _ _ AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` total_time ` ASC \
' ,
slowest :
' \
SELECT \
DISTINCT ( ` user_id ` ) AS ` user_id ` , \
( \
SELECT \
` id ` \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` total_time ` DESC \
LIMIT 1 \
) AS ` id ` , \
( \
SELECT \
` total_time ` \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` total_time ` DESC \
LIMIT 1 \
) AS ` total_time ` \
FROM ` judge_state ` ` outer_table ` \
WHERE \
` problem_id ` = _ _PROBLEM _ID _ _ AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` total_time ` DESC \
' ,
shortest :
' \
SELECT \
DISTINCT ( ` user_id ` ) AS ` user_id ` , \
( \
SELECT \
` id ` \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY LENGTH ( ` code ` ) ASC \
LIMIT 1 \
) AS ` id ` , \
( \
SELECT \
LENGTH ( ` code ` ) \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY LENGTH ( ` code ` ) ASC \
LIMIT 1 \
) AS ` code_length ` \
FROM ` judge_state ` ` outer_table ` \
WHERE \
` problem_id ` = _ _PROBLEM _ID _ _ AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` code_length ` ASC \
' ,
longest :
' \
SELECT \
DISTINCT ( ` user_id ` ) AS ` user_id ` , \
( \
SELECT \
` id ` \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY LENGTH ( ` code ` ) DESC \
LIMIT 1 \
) AS ` id ` , \
( \
SELECT \
LENGTH ( ` code ` ) \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY LENGTH ( ` code ` ) DESC \
LIMIT 1 \
) AS ` code_length ` \
FROM ` judge_state ` ` outer_table ` \
WHERE \
` problem_id ` = _ _PROBLEM _ID _ _ AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` code_length ` DESC \
' ,
earliest :
' \
SELECT \
DISTINCT ( ` user_id ` ) AS ` user_id ` , \
( \
SELECT \
` id ` \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` submit_time ` ASC \
LIMIT 1 \
) AS ` id ` , \
( \
SELECT \
` submit_time ` \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` submit_time ` ASC \
LIMIT 1 \
) AS ` submit_time ` \
FROM ` judge_state ` ` outer_table ` \
WHERE \
` problem_id ` = _ _PROBLEM _ID _ _ AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` submit_time ` ASC \
' ,
min :
' \
SELECT \
DISTINCT ( ` user_id ` ) AS ` user_id ` , \
( \
SELECT \
` id ` \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` max_memory ` ASC \
LIMIT 1 \
) AS ` id ` , \
( \
SELECT \
` max_memory ` \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` max_memory ` ASC \
LIMIT 1 \
) AS ` max_memory ` \
FROM ` judge_state ` ` outer_table ` \
WHERE \
` problem_id ` = _ _PROBLEM _ID _ _ AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` max_memory ` ASC \
' ,
max :
' \
SELECT \
DISTINCT ( ` user_id ` ) AS ` user_id ` , \
( \
SELECT \
` id ` \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` max_memory ` ASC \
LIMIT 1 \
) AS ` id ` , \
( \
SELECT \
` max_memory ` \
FROM ` judge_state ` ` inner_table ` \
WHERE ` problem_id ` = ` outer_table ` . ` problem_id ` AND ` user_id ` = ` outer_table ` . ` user_id ` AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` max_memory ` ASC \
LIMIT 1 \
) AS ` max_memory ` \
FROM ` judge_state ` ` outer_table ` \
WHERE \
` problem_id ` = _ _PROBLEM _ID _ _ AND ` status ` = "Accepted" AND ` type ` = 0 \
ORDER BY ` max_memory ` DESC \
'
} ;
let Sequelize = require ( 'sequelize' ) ;
let db = syzoj . db ;
let User = syzoj . model ( 'user' ) ;
let File = syzoj . model ( 'file' ) ;
let model = db . define ( 'problem' , {
id : { type : Sequelize . INTEGER , primaryKey : true , autoIncrement : true } ,
title : { type : Sequelize . STRING ( 80 ) } ,
user _id : {
type : Sequelize . INTEGER ,
references : {
model : 'user' ,
key : 'id'
}
} ,
publicizer _id : {
type : Sequelize . INTEGER ,
references : {
model : 'user' ,
key : 'id'
}
} ,
is _anonymous : { type : Sequelize . BOOLEAN } ,
description : { type : Sequelize . TEXT } ,
input _format : { type : Sequelize . TEXT } ,
output _format : { type : Sequelize . TEXT } ,
example : { type : Sequelize . TEXT } ,
limit _and _hint : { type : Sequelize . TEXT } ,
time _limit : { type : Sequelize . INTEGER } ,
memory _limit : { type : Sequelize . INTEGER } ,
additional _file _id : { type : Sequelize . INTEGER } ,
ac _num : { type : Sequelize . INTEGER } ,
submit _num : { type : Sequelize . INTEGER } ,
is _public : { type : Sequelize . BOOLEAN } ,
file _io : { type : Sequelize . BOOLEAN } ,
file _io _input _name : { type : Sequelize . TEXT } ,
file _io _output _name : { type : Sequelize . TEXT } ,
type : {
type : Sequelize . ENUM ,
values : [ 'traditional' , 'submit-answer' , 'interaction' ]
}
} , {
timestamps : false ,
tableName : 'problem' ,
indexes : [
{
fields : [ 'title' ] ,
} ,
{
fields : [ 'user_id' ] ,
}
]
} ) ;
let Model = require ( './common' ) ;
class Problem extends Model {
static async create ( val ) {
return Problem . fromRecord ( Problem . model . build ( Object . assign ( {
title : '' ,
user _id : '' ,
publicizer _id : '' ,
is _anonymous : false ,
description : '' ,
input _format : '' ,
output _format : '' ,
example : '' ,
limit _and _hint : '' ,
time _limit : syzoj . config . default . problem . time _limit ,
memory _limit : syzoj . config . default . problem . memory _limit ,
ac _num : 0 ,
submit _num : 0 ,
is _public : false ,
file _io : false ,
file _io _input _name : '' ,
file _io _output _name : '' ,
type : 'traditional'
} , val ) ) ) ;
}
async loadRelationships ( ) {
this . user = await User . fromID ( this . user _id ) ;
this . publicizer = await User . fromID ( this . publicizer _id ) ;
this . additional _file = await File . fromID ( this . additional _file _id ) ;
}
async isAllowedEditBy ( user ) {
if ( ! user ) return false ;
if ( await user . hasPrivilege ( 'manage_problem' ) ) return true ;
return this . user _id === user . id ;
}
async isAllowedUseBy ( user ) {
if ( this . is _public ) return true ;
if ( ! user ) return false ;
if ( await user . hasPrivilege ( 'manage_problem' ) ) return true ;
return this . user _id === user . id ;
}
async isAllowedManageBy ( user ) {
if ( ! user ) return false ;
if ( await user . hasPrivilege ( 'manage_problem' ) ) return true ;
return user . is _admin ;
}
getTestdataPath ( ) {
return syzoj . utils . resolvePath ( syzoj . config . upload _dir , 'testdata' , this . id . toString ( ) ) ;
}
async updateTestdata ( path , noLimit ) {
let AdmZip = require ( 'adm-zip' ) ;
let zip = new AdmZip ( path ) ;
let unzipSize = 0 ;
for ( let x of zip . getEntries ( ) ) unzipSize += x . header . size ;
if ( ! noLimit && unzipSize > syzoj . config . limit . testdata ) throw new ErrorMessage ( '数据包太大。' ) ;
let dir = this . getTestdataPath ( ) ;
let fs = Promise . promisifyAll ( require ( 'fs-extra' ) ) ;
await fs . removeAsync ( dir ) ;
await fs . ensureDirAsync ( dir ) ;
zip . extractAllTo ( dir ) ;
await fs . moveAsync ( path , dir + '.zip' , { overwrite : true } ) ;
}
async uploadTestdataSingleFile ( filename , filepath , size , noLimit ) {
let dir = this . getTestdataPath ( ) ;
let fs = Promise . promisifyAll ( require ( 'fs-extra' ) ) , path = require ( 'path' ) ;
await fs . ensureDirAsync ( dir ) ;
let oldSize = 0 ;
let list = await this . listTestdata ( ) ;
if ( list ) {
for ( let file of list . files ) if ( file . filename !== filename ) oldSize += file . size ;
}
if ( ! noLimit && oldSize + size > syzoj . config . limit . testdata ) throw new ErrorMessage ( '数据包太大。' ) ;
await fs . moveAsync ( filepath , path . join ( dir , filename ) , { overwrite : true } ) ;
await fs . removeAsync ( dir + '.zip' ) ;
}
async deleteTestdataSingleFile ( filename ) {
let dir = this . getTestdataPath ( ) ;
let fs = Promise . promisifyAll ( require ( 'fs-extra' ) ) , path = require ( 'path' ) ;
await fs . removeAsync ( path . join ( dir , filename ) ) ;
await fs . removeAsync ( dir + '.zip' ) ;
}
async makeTestdataZip ( ) {
let dir = this . getTestdataPath ( ) ;
if ( ! await syzoj . utils . isDir ( dir ) ) throw new ErrorMessage ( '无测试数据。' ) ;
let AdmZip = require ( 'adm-zip' ) ;
let zip = new AdmZip ( ) ;
let list = await this . listTestdata ( ) ;
for ( let file of list . files ) zip . addLocalFile ( require ( 'path' ) . join ( dir , file . filename ) , '' , file . filename ) ;
zip . writeZip ( dir + '.zip' ) ;
}
async hasSpecialJudge ( ) {
try {
let fs = Promise . promisifyAll ( require ( 'fs-extra' ) ) ;
let dir = this . getTestdataPath ( ) ;
let list = await fs . readdirAsync ( dir ) ;
return list . includes ( 'spj.js' ) || list . find ( x => x . startsWith ( 'spj_' ) ) !== undefined ;
} catch ( e ) {
return false ;
}
}
async listTestdata ( ) {
try {
let fs = Promise . promisifyAll ( require ( 'fs-extra' ) ) , path = require ( 'path' ) ;
let dir = this . getTestdataPath ( ) ;
let list = await fs . readdirAsync ( dir ) ;
list = await list . mapAsync ( async x => {
let stat = await fs . statAsync ( path . join ( dir , x ) ) ;
if ( ! stat . isFile ( ) ) return undefined ;
return {
filename : x ,
size : stat . size
} ;
} ) ;
list = list . filter ( x => x ) ;
let res = {
files : list ,
zip : null
} ;
try {
let stat = await fs . statAsync ( this . getTestdataPath ( ) + '.zip' ) ;
if ( stat . isFile ( ) ) {
res . zip = {
size : stat . size
} ;
}
} catch ( e ) {
if ( list ) {
res . zip = {
size : null
} ;
}
}
return res ;
} catch ( e ) {
return null ;
}
}
async updateFile ( path , type , noLimit ) {
let file = await File . upload ( path , type , noLimit ) ;
if ( type === 'additional_file' ) {
this . additional _file _id = file . id ;
}
await this . save ( ) ;
}
async validate ( ) {
if ( this . time _limit <= 0 ) return 'Invalid time limit' ;
if ( this . time _limit > syzoj . config . limit . time _limit ) return 'Time limit too large' ;
if ( this . memory _limit <= 0 ) return 'Invalid memory limit' ;
if ( this . memory _limit > syzoj . config . limit . memory _limit ) return 'Memory limit too large' ;
let filenameRE = /^[\w \-\+\.]*$/ ;
if ( this . file _io _input _name && ! filenameRE . test ( this . file _io _input _name ) ) return 'Invalid input file name' ;
if ( this . file _io _output _name && ! filenameRE . test ( this . file _io _output _name ) ) return 'Invalid output file name' ;
if ( this . file _io ) {
if ( ! this . file _io _input _name ) return 'No input file name' ;
if ( ! this . file _io _output _name ) return 'No output file name' ;
}
return null ;
}
async getJudgeState ( user , acFirst ) {
if ( ! user ) return null ;
let JudgeState = syzoj . model ( 'judge_state' ) ;
let where = {
user _id : user . id ,
problem _id : this . id
} ;
if ( acFirst ) {
where . status = 'Accepted' ;
let state = await JudgeState . findOne ( {
where : where ,
order : [ [ 'submit_time' , 'desc' ] ]
} ) ;
if ( state ) return state ;
}
if ( where . status ) delete where . status ;
return await JudgeState . findOne ( {
where : where ,
order : [ [ 'submit_time' , 'desc' ] ]
} ) ;
}
// type: fastest / slowest / shortest / longest / earliest
async countStatistics ( type ) {
let statement = statisticsStatements [ type ] ;
if ( ! statement ) return null ;
statement = statement . replace ( '__PROBLEM_ID__' , this . id ) ;
return await db . countQuery ( statement ) ;
}
// type: fastest / slowest / shortest / longest / earliest
async getStatistics ( type , paginate ) {
let statistics = {
type : type ,
judge _state : null ,
scoreDistribution : null ,
prefixSum : null ,
suffixSum : null
} ;
let statement = statisticsStatements [ type ] ;
if ( ! statement ) return null ;
statement = statement . replace ( '__PROBLEM_ID__' , this . id ) ;
let a ;
if ( ! paginate . pageCnt ) a = [ ] ;
else a = ( await db . query ( statement + ` LIMIT ${ paginate . perPage } OFFSET ${ ( paginate . currPage - 1 ) * paginate . perPage } ` ) ) [ 0 ] ;
let JudgeState = syzoj . model ( 'judge_state' ) ;
statistics . judge _state = await a . mapAsync ( async x => JudgeState . fromID ( x . id ) ) ;
a = ( await db . query ( 'SELECT `score`, COUNT(*) AS `count` FROM `judge_state` WHERE `problem_id` = __PROBLEM_ID__ AND `type` = 0 AND `pending` = 0 GROUP BY `score`' . replace ( '__PROBLEM_ID__' , this . id ) ) ) [ 0 ] ;
let scoreCount = [ ] ;
for ( let score of a ) {
score . score = Math . min ( Math . round ( score . score ) , 100 ) ;
scoreCount [ score . score ] = score . count ;
}
if ( scoreCount [ 0 ] === undefined ) scoreCount [ 0 ] = 0 ;
if ( scoreCount [ 100 ] === undefined ) scoreCount [ 100 ] = 0 ;
statistics . scoreDistribution = [ ] ;
for ( let i = 0 ; i < scoreCount . length ; i ++ ) {
if ( scoreCount [ i ] !== undefined ) statistics . scoreDistribution . push ( { score : i , count : scoreCount [ i ] } ) ;
}
statistics . prefixSum = JSON . parse ( JSON . stringify ( statistics . scoreDistribution ) ) ;
statistics . suffixSum = JSON . parse ( JSON . stringify ( statistics . scoreDistribution ) ) ;
for ( let i = 1 ; i < statistics . prefixSum . length ; i ++ ) {
statistics . prefixSum [ i ] . count += statistics . prefixSum [ i - 1 ] . count ;
}
for ( let i = statistics . prefixSum . length - 1 ; i >= 1 ; i -- ) {
statistics . suffixSum [ i - 1 ] . count += statistics . suffixSum [ i ] . count ;
}
return statistics ;
}
async getTags ( ) {
let ProblemTagMap = syzoj . model ( 'problem_tag_map' ) ;
let maps = await ProblemTagMap . query ( null , {
problem _id : this . id
} ) ;
let ProblemTag = syzoj . model ( 'problem_tag' ) ;
let res = await maps . mapAsync ( async map => {
return ProblemTag . fromID ( map . tag _id ) ;
} ) ;
res . sort ( ( a , b ) => {
return a . color > b . color ? 1 : - 1 ;
} ) ;
return res ;
}
async setTags ( newTagIDs ) {
let ProblemTagMap = syzoj . model ( 'problem_tag_map' ) ;
let oldTagIDs = ( await this . getTags ( ) ) . map ( x => x . id ) ;
let delTagIDs = oldTagIDs . filter ( x => ! newTagIDs . includes ( x ) ) ;
let addTagIDs = newTagIDs . filter ( x => ! oldTagIDs . includes ( x ) ) ;
for ( let tagID of delTagIDs ) {
let map = await ProblemTagMap . findOne ( { where : {
problem _id : this . id ,
tag _id : tagID
} } ) ;
await map . destroy ( ) ;
}
for ( let tagID of addTagIDs ) {
let map = await ProblemTagMap . create ( {
problem _id : this . id ,
tag _id : tagID
} ) ;
await map . save ( ) ;
}
}
async changeID ( id ) {
id = parseInt ( id ) ;
await db . query ( 'UPDATE `problem` SET `id` = ' + id + ' WHERE `id` = ' + this . id ) ;
await db . query ( 'UPDATE `judge_state` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this . id ) ;
await db . query ( 'UPDATE `problem_tag_map` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this . id ) ;
let Contest = syzoj . model ( 'contest' ) ;
let contests = await Contest . all ( ) ;
for ( let contest of contests ) {
let problemIDs = await contest . getProblems ( ) ;
let flag = false ;
for ( let i in problemIDs ) {
if ( problemIDs [ i ] === this . id ) {
problemIDs [ i ] = id ;
flag = true ;
}
}
if ( flag ) {
await contest . setProblemsNoCheck ( problemIDs ) ;
await contest . save ( ) ;
}
}
let oldTestdataDir = this . getTestdataPath ( ) , oldTestdataZip = oldTestdataDir + '.zip' ;
this . id = id ;
// Move testdata
let newTestdataDir = this . getTestdataPath ( ) , newTestdataZip = newTestdataDir + '.zip' ;
let fs = Promise . promisifyAll ( require ( 'fs-extra' ) ) ;
if ( await syzoj . utils . isDir ( oldTestdataDir ) ) {
await fs . moveAsync ( oldTestdataDir , newTestdataDir ) ;
}
if ( await syzoj . utils . isFile ( oldTestdataZip ) ) {
await fs . moveAsync ( oldTestdataZip , newTestdataZip ) ;
}
await this . save ( ) ;
}
getModel ( ) { return model ; }
}
Problem . model = model ;
module . exports = Problem ;