package models; import com.avaje.ebean.Ebean; import com.avaje.ebean.SqlQuery; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.zeroturnaround.zip.ZipUtil; import play.Logger; import play.data.validation.Constraints; import play.db.ebean.Model; import play.libs.Json; import utils.FileCopy; import utils.Markup; import javax.persistence.*; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Date; import java.util.List; import java.util.Scanner; import java.util.UUID; @Entity public class Problem extends Model { @Id public Long id; @Column(unique = true, nullable = false) public String slug = UUID.randomUUID().toString(); public int status = 1; // 0 normal; 1 editing; 2 pending; 3 view only; 4 deleted; public String title; @Lob public String description; public String tags; public String source; public int timeLimit; // in ms. 0 for not specified. public int memoryLimit; // in MB. 0 for not specified. public boolean specialJudge; public String resourcesHash; public Date createTime = new Date(); public Date lastModifyTime = new Date(); @ManyToOne public User author; @OneToMany(mappedBy = "problem") public List<ContestProblem> contests; public boolean showInProblems = true; @JsonIgnore @OneToMany(mappedBy = "problem") public List<Solution> solutions; @JsonIgnore @OneToMany(mappedBy = "problem") public List<Discussion> discussions; @JsonIgnore @OneToMany(mappedBy = "problem") public List<ProblemFollower> followedBy; @JsonIgnore @OneToMany(mappedBy = "problem") public List<ProblemStar> starredBy; public static Finder<Long, Problem> find = new Finder<>(Long.class, Problem.class); public static Problem findBySlug(String slug) { return find.where().eq("slug", slug).findUnique(); } public String getDescriptionHTML() { String html = Markup.fromMarkdown(description); html = html.replaceAll("<h1>(.*?)</h1>", "<h4>$1</h4>"); html = html.replaceAll("<h2>(.*?)</h2>", "<h5>$1</h5>"); html = html.replaceAll("<h3>(.*?)</h3>", "<h6>$1</h6>"); html = html.replaceAll("__ASSETS__", "/problem/" + id + "/assets"); return html; } public double getQualityRating() { // TODO: Cache this result. List<ProblemVote> votes = ProblemVote.find.where().eq("problem", this).findList(); long sum = 0; for (ProblemVote vote : votes) { sum += vote.rating; } return 1.0 * sum / votes.size(); } public double getDifficultyRating() { // TODO: Cache this result. List<ProblemVote> votes = ProblemVote.find.where().eq("problem", this).findList(); long sum = 0; for (ProblemVote vote : votes) { sum += vote.difficulty; } return 1.0 * sum / votes.size(); } public long getNumberOfSolvers() { String sql = "SELECT COUNT(DISTINCT `user_id`) AS 'solvers' FROM `solution`" + "WHERE `result` = 200 AND `problem_id` = :id"; SqlQuery sqlQuery = Ebean.createSqlQuery(sql); sqlQuery.setParameter("id", id); return sqlQuery.findUnique().getInteger("solvers"); } public boolean isSolvedBy(User user) { String sql = "SELECT COUNT(*) AS 'num' FROM `solution`" + "WHERE `result` = 200 AND `problem_id` = :problem AND `user_id` = :user"; SqlQuery sqlQuery = Ebean.createSqlQuery(sql); sqlQuery.setParameter("problem", id); sqlQuery.setParameter("user", user.id); return sqlQuery.findUnique().getInteger("num") > 0; } /** * Get the resource folder of the problem. * @return Path to the problem's resource folder. */ @JsonIgnore public Path getResourcesPath() { return Paths.get("upload/problem/" + id); } /** * Get the asset folder of the problem. * @return Path to the problem's asset folder. */ @JsonIgnore public Path getAssetPath() { return Paths.get("upload/assets/" + id); } /** * This method will return the problem data in a JSON string, which is used * for output and input problem from systems. * @return A JSON string */ public String toJson() { ObjectNode json = Json.newObject(); json.put("title", title); json.put("description", description); json.put("tags", tags); json.put("source", source); json.put("timeLimit", timeLimit); json.put("memoryLimit", memoryLimit); json.put("specialJudge", specialJudge); json.put("resourcesHash", resourcesHash); return json.toString(); } /** * Generate a Problem object from JSON data. * @param jsonString A string as JSON format. * @return Generated problem. */ public static Problem fromJson(String jsonString) { JsonNode json = Json.parse(jsonString); Problem problem = new Problem(); problem.title = json.get("title").asText(); problem.description = json.get("description").asText(); problem.tags = json.get("tags").asText(); problem.source = json.get("source").asText(); problem.timeLimit = json.get("timeLimit").asInt(); problem.memoryLimit = json.get("memoryLimit").asInt(); problem.specialJudge = json.get("specialJudge").asBoolean(); problem.resourcesHash = json.get("resourcesHash").asText(); return problem; } public File problemZipFile() throws IOException { String problemId = "problem_" + id; Logger.info("Create problem zip package named " + problemId); Path tempDirectory = Files.createTempDirectory(problemId); // Write problem data file. File problemFile = new File(tempDirectory.toFile(), "data.json"); PrintWriter writer = new PrintWriter(problemFile, "UTF-8"); writer.println(toJson()); writer.close(); // Copy files. FileCopy.copyDirectory(getResourcesPath(), tempDirectory.resolve("resources")); FileCopy.copyDirectory(getAssetPath(), tempDirectory.resolve("assets")); // Create zip package. File zip = File.createTempFile(problemId + "_", ".zip"); ZipUtil.pack(tempDirectory.toFile(), zip); return zip; } public static Problem importZipFile(File importFile) throws IOException { Path tempDirectory = Files.createTempDirectory("imported_problem"); Logger.info("Create temporary folder handle packed problem at " + tempDirectory.toAbsolutePath().toString() + "."); ZipUtil.unpack(importFile, tempDirectory.toFile()); File problemFile = new File(tempDirectory.toFile(), "data.json"); Logger.info("Read from problem file " + problemFile.getAbsolutePath() + "."); String problemJson = ""; Scanner scanner = new Scanner(problemFile); while (scanner.hasNextLine()) { problemJson += scanner.nextLine() + "\n"; } Problem problem = Problem.fromJson(problemJson); problem.save(); Logger.info("Problem created with id " + problem.id); FileCopy.copyDirectory(tempDirectory.resolve("resources"), problem.getResourcesPath()); FileCopy.copyDirectory(tempDirectory.resolve("assets"), problem.getAssetPath()); return problem; } }