import scala.actors.Actor import scala.actors.Actor._ import java.util.Properties import java.util.Date import java.text.DateFormat import java.text.SimpleDateFormat import java.text.DecimalFormat import java.io._ import javax.mail._ import javax.mail.internet._ import javax.mail.Flags.Flag class Submission(email:String, labels:String) { val Email = email val Labels = labels } case class StudentRecord(ID:String, Email: String, BestAccuracy: Double, LastAccuracy: Double, BestSubmission: Date, NumOfSubmission: Int); object Props { val trueLabelsFile = "D:/Users/Dropbox/Dropbox/TA/project1/test.labels.txt" val scoreSheetFile = "D:/Users/Dropbox/Dropbox/TA/project1/score-sheet.csv" val scoreWebPage = "D:/Users/Dropbox/Dropbox/Public/mail-judge/leaderboard_comp4331.html" val gmailAccount = "FILL GMAIL ACCOUNT" val gmailPassword = "FILL PASSWORD" val gmailAddress = gmailAccount + "@gmail.com" val checkSleep = 30000 // check email every 1 minute val dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss") val accuracyFormat = new DecimalFormat("##.####") } case class Status(time:Date, ID:String, accuracy:Double); object EmailJudger { val labelSource = scala.io.Source.fromFile(Props.trueLabelsFile) val trueLabels = labelSource.mkString.trim().split("\n").map(_.trim()) val statusList = new scala.collection.mutable.ArrayBuffer[Status]() val restartDate = new Date() def main(args: Array[String]): Unit = { println("hello world") emailSenderActor.start() errorActor.start() parseEvalActor.start() def checkEmail() = { val props = System.getProperties() val session = Session.getInstance(props, null) val store = session.getStore("imaps") store.connect("imap.gmail.com", -1, Props.gmailAccount, Props.gmailPassword) val fod = store.getDefaultFolder() val folder = fod.getFolder("INBOX") folder.open(Folder.READ_WRITE) val totalMessages = folder.getMessageCount(); var iter = totalMessages //var seencnt = 0 while (iter>0) { val msg = folder.getMessage(iter) if (msg.isSet(Flag.SEEN)) { //seencnt += 1 //println(seencnt) //iter -= 1; //if (seencnt >20) { iter = 0 // the bug seems due to "not pure message", a non-pure gives on error message //} } else { val date = msg.getReceivedDate() val from = msg.getFrom()(0).asInstanceOf[InternetAddress].getAddress() println(from) println(date) val p = msg.asInstanceOf[Part] var content = "" try { content = p.getContent().asInstanceOf[String] val sub = new Submission(from, content) parseEvalActor ! (sub, date) } catch { case e: ClassCastException => msg.setFlag(Flag.SEEN, true) errorActor ! (from, "Your email body is not pure text") } iter -= 1 } } store.close() } //emailSenderActor ! ("zhuyin.nju@gmail.com", "test, test") while (true) { println("check email") try { checkEmail() } catch { case e: javax.mail.MessagingException => { println(e.toString()); println("Cannot connect to emailbox. But we roll on, get another try after several seconds") Thread.sleep(Props.checkSleep) } case e2: Exception => { println("I found a new exception which I cannot handle. sending a message to admin!") errorActor ! ("zhuyin.nju@gmail.com", "judge system error!\n\n" + e2.toString()) throw e2; } } println("sleep") Thread.sleep(Props.checkSleep) } } val updateScoreSheetActor = actor { loop { receive { case (email:String, recDate:Date, accuracy:Double) => val scoreSheetSource = scala.io.Source.fromFile(Props.scoreSheetFile) val lines = scoreSheetSource.mkString.trim().split("\n").map(_.trim()) val records = lines.drop(1).map( line => { val s = line.split(",") var date:Date = null try { date = Props.dateFormat.parse(s(4)) } catch { case e:java.text.ParseException => { date = null; // simple put null } } StudentRecord(s(0), s(1), s(2).toDouble, s(3).toDouble, date, s(5).toInt) } ) val idx = records.findIndexOf(r => r.Email == email) println("index of the email = " + idx.toString) if (idx == -1) { println(email + " does not match emails in database") errorActor ! (email, " your email address is not in our database") } else { val oldrec = records(idx) def max(x:Double, y:Double) = if (x>y) x else y println(oldrec.ID + " " + oldrec.NumOfSubmission) //accuracy = int(accuracy*10000)/10000 val acc4 = (accuracy*10000).toInt/10000.0 records(idx) = if (oldrec.BestAccuracy >= acc4) StudentRecord(oldrec.ID, oldrec.Email, oldrec.BestAccuracy, acc4, oldrec.BestSubmission, oldrec.NumOfSubmission + 1) else StudentRecord(oldrec.ID, oldrec.Email, acc4, acc4, recDate, oldrec.NumOfSubmission + 1); statusList += Status(recDate, oldrec.ID, acc4) // write the csv file back def date2string(date:Date) = if (date == null) "-1" else Props.dateFormat.format(date) def record2line (rec:StudentRecord) = (if (rec.ID.length() == 7) "0"+rec.ID else rec.ID) + "," + rec.Email + "," + Props.accuracyFormat.format(rec.BestAccuracy) + "," + Props.accuracyFormat.format(rec.LastAccuracy) + "," + date2string(rec.BestSubmission) + "," + rec.NumOfSubmission.toString() //println(record2line(records(idx))) val fw2 = new FileWriter(Props.scoreSheetFile) fw2.write("ID,EMAIL,Best-Accuracy,Last-Accuracy,Best-Submission-Date,NUM-OF-SUBMISSIONS\n") records.foreach( rec => fw2.write(record2line(rec) + "\n")) fw2.close() // update web page def record2webline (rec:StudentRecord) = "" + "" + rec.ID + "" + Props.accuracyFormat.format(rec.BestAccuracy) + "" + Props.accuracyFormat.format(rec.LastAccuracy) + ""+ date2string(rec.BestSubmission) + ""+rec.NumOfSubmission.toString() + "\n" //val sortedRecords = records.sortBy(rec => (- rec.BestAccuracy, rec.BestSubmission)) // sort by accuracy and the last submission time val sortedRecords = records.sortBy(rec => (- rec.BestAccuracy)) val fw = new FileWriter(Props.scoreWebPage) fw.write("\n") fw.write("Last updated on " + (new Date()).toString() + "


\n") fw.write("The email judge is restarted at " + Props.dateFormat.format(restartDate) + ".
") fw.write("Last few submissions:
") var last = statusList.size - 1 while (last >= 0 && last > statusList.size - 5) { fw.write(date2string(statusList(last).time) + "     ") fw.write(statusList(last).ID + "     ") fw.write(Props.accuracyFormat.format(statusList(last).accuracy) + "
\n") last = last - 1 } fw.write("


") fw.write("") fw.write( "" ); //fw.write("ID,Best-Accuracy,Last-Accuracy,Number-of-submissions
\n") sortedRecords.filter(rec => rec.BestAccuracy > 0).foreach( rec => fw.write(record2webline(rec) + "\n")) fw.write("
ID Best-Accuracy Last-Accuracy Best-Submission-Date Number-of-submissions
") fw.close() } } } } val emailSenderActor = actor { loop { receive { case (emailTo:java.lang.String, content:String) => { val props = System.getProperties() props.put("mail.smtp.host", "smtp.gmail.com") val session = Session.getInstance(props, null); val msg = new MimeMessage(session); msg.setFrom(new InternetAddress(Props.gmailAddress)) msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(emailTo, false).asInstanceOf[Array[Address]]) msg.setText(content) msg.setHeader("X-Mailer", "robot") msg.setSentDate(new Date()) val t = session.getTransport("smtps") t.connect("smtp.gmail.com", Props.gmailAccount, Props.gmailPassword) t.sendMessage(msg, msg.getAllRecipients()) t.close() } } } } val errorActor = actor { loop { receive { case (email: String, msg: String) => { emailSenderActor ! (email, "ERROR: " + msg) } } } } val parseEvalActor = actor { loop { receive { case (sub:Submission, date:Date) => val email = sub.Email val labels = sub.Labels.trim().split("\n") map (_.trim()) if (trueLabels.length != labels.length) { errorActor ! (sub.Email, "your submission does not contain 30,000 lines EXACTLY.") } else if (labels.exists(line => if (line != "O0" && line != "O1") true else false)) { errorActor ! (sub.Email, "your email body contains strings other than O0 and O1") } else { val correct = labels zip trueLabels map( pair => if (pair._1 == pair._2) 1 else 0) sum val n = labels.length val accuracy = correct * 1.0 / n // send email to the student emailSenderActor ! (email, "your accuracy for this submission is " + accuracy.toString() + "\n\n" + "please check the latest leaderboard at: http://dl.dropbox.com/u/7090102/mail-judge/leaderboard_comp4331.html") println("your accuracy for this submission is " + accuracy.toString()) // update the webpage updateScoreSheetActor ! (email, date, accuracy) } } } } }