diff --git a/.gitignore b/.gitignore --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *~ log +tmp + diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -62,8 +62,8 @@ return true if session[:user_id]==nil user = User.find(session[:user_id], :include => :site) return true if user==nil or user.site == nil - if user.site.finished? - flash[:notice] = 'Error: the contest on your site is over.' + if user.contest_finished? + flash[:notice] = 'Error: the contest you are participating is over.' redirect_to :back return false end diff --git a/app/controllers/contests_controller.rb b/app/controllers/contests_controller.rb new file mode 100644 --- /dev/null +++ b/app/controllers/contests_controller.rb @@ -0,0 +1,30 @@ +class ContestsController < ApplicationController + + before_filter :admin_authorization + + def index + end + + def user_stat + if not Configuration.indv_contest_mode? + redirect_to :action => 'index' and return + end + + @users = User.find(:all) + @start_times = {} + UserContestStat.find(:all).each do |stat| + @start_times[stat.user_id] = stat.started_at + end + end + + def clear_all_stat + if not Configuration.indv_contest_mode? + redirect_to :action => 'index' and return + end + + UserContestStat.delete_all() + flash[:notice] = 'All start time statistic cleared.' + redirect_to :action => 'index' + end + +end diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb --- a/app/controllers/login_controller.rb +++ b/app/controllers/login_controller.rb @@ -9,12 +9,9 @@ def login if user = User.authenticate(params[:login], params[:password]) session[:user_id] = user.id + session[:admin] = user.admin? + UserContestStat.update_user_start_time(user) redirect_to :controller => 'main', :action => 'list' - if user.admin? - session[:admin] = true - else - session[:admin] = false - end else flash[:notice] = 'Wrong password' redirect_to :controller => 'main', :action => 'login' diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -1,7 +1,5 @@ class MainController < ApplicationController - SYSTEM_MODE_CONF_KEY = 'system.mode' - before_filter :authenticate, :except => [:index, :login] before_filter :check_viewability, :except => [:index, :login] @@ -59,8 +57,7 @@ end @submission.submitted_at = Time.new.gmtime - if Configuration[SYSTEM_MODE_CONF_KEY]=='contest' and - user.site!=nil and user.site.finished? + if Configuration.time_limit_mode? and user.contest_finished? @submission.errors.add_to_base "The contest is over." prepare_list_information render :action => 'list' and return diff --git a/app/controllers/test_controller.rb b/app/controllers/test_controller.rb --- a/app/controllers/test_controller.rb +++ b/app/controllers/test_controller.rb @@ -1,7 +1,5 @@ class TestController < ApplicationController - SYSTEM_MODE_CONF_KEY = 'system.mode' - before_filter :authenticate, :check_viewability # @@ -26,8 +24,8 @@ render :action => 'index' and return end - if Configuration[SYSTEM_MODE_CONF_KEY]=='contest' - if @user.site!=nil and @user.site.finished? + if Configuration.time_limit_mode? + if @user.contest_finished? @submitted_test_request.errors.add_to_base('Contest is over.') prepare_index_information render :action => 'index' and return diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,8 +1,6 @@ # Methods added to this helper will be available to all templates in the application. module ApplicationHelper - SYSTEM_MODE_CONF_KEY = 'system.mode' - def user_header menu_items = '' user = User.find(session[:user_id]) @@ -12,10 +10,11 @@ menu_items << "Administrative task: " append_to menu_items, '[Announcements]', 'announcements', 'index' append_to menu_items, '[Msg console]', 'messages', 'console' - append_to menu_items, '[Problem admin]', 'problems', 'index' - append_to menu_items, '[User admin]', 'user_admin', 'index' + append_to menu_items, '[Problems]', 'problems', 'index' + append_to menu_items, '[Users]', 'user_admin', 'index' append_to menu_items, '[Results]', 'user_admin', 'user_stat' append_to menu_items, '[Graders]', 'graders', 'list' + append_to menu_items, '[Contests]', 'contests', 'index' append_to menu_items, '[Sites]', 'sites', 'index' append_to menu_items, '[System config]', 'configurations', 'index' menu_items << "
" @@ -57,6 +56,12 @@ st + time.strftime("%X") end + def format_short_duration(duration) + return '' if duration==nil + d = duration.to_f + return Time.at(d).gmtime.strftime("%X") + end + def read_textfile(fname,max_size=2048) begin File.open(fname).read(max_size) @@ -71,27 +76,25 @@ # # if the contest is over - if Configuration[SYSTEM_MODE_CONF_KEY]=='contest' - if user.site!=nil and user.site.finished? + if Configuration.time_limit_mode? + if user.contest_finished? header = < THE CONTEST IS OVER CONTEST_OVER end - if !user.site.started + if !user.contest_started? time_left = "  " + (t 'title_bar.contest_not_started') else - if user.site!=nil - time_left = "  " + (t 'title_bar.remaining_time') + - " #{Time.at(user.site.time_left).gmtime.strftime("%X")}" - end + time_left = "  " + (t 'title_bar.remaining_time') + + " #{format_short_duration(user.contest_time_left)}" end end # # if the contest is in the anaysis mode - if Configuration[SYSTEM_MODE_CONF_KEY]=='analysis' + if Configuration.analysis_mode? header = < ANALYSIS MODE diff --git a/app/helpers/contests_helper.rb b/app/helpers/contests_helper.rb new file mode 100644 --- /dev/null +++ b/app/helpers/contests_helper.rb @@ -0,0 +1,2 @@ +module ContestsHelper +end diff --git a/app/models/configuration.rb b/app/models/configuration.rb --- a/app/models/configuration.rb +++ b/app/models/configuration.rb @@ -59,9 +59,8 @@ end def self.show_tasks_to?(user) - mode = get(SYSTEM_MODE_CONF_KEY) - if (mode=='contest') - return false if (user.site!=nil) and (user.site.started!=true) + if time_limit_mode? + return false if not user.contest_started? end return true end @@ -89,10 +88,48 @@ return @@task_grading_info end - def self.contest_mode + def self.standard_mode? + return get(SYSTEM_MODE_CONF_KEY) == 'standard' + end + + def self.contest_mode? return get(SYSTEM_MODE_CONF_KEY) == 'contest' end + def self.indv_contest_mode? + return get(SYSTEM_MODE_CONF_KEY) == 'indv-contest' + end + + def self.time_limit_mode? + mode = get(SYSTEM_MODE_CONF_KEY) + return ((mode == 'contest') or (mode == 'indv-contest')) + end + + def self.analysis_mode? + return get(SYSTEM_MODE_CONF_KEY) == 'analysis' + end + + def self.contest_time_limit + contest_time_str = Configuration['contest.time_limit'] + + if not defined? @@contest_time_str + @@contest_time_str = nil + end + + if @@contest_time_str != contest_time_str + @@contest_time_str = contest_time_str + if tmatch = /(\d+):(\d+)/.match(contest_time_str) + h = tmatch[1].to_i + m = tmatch[2].to_i + + @@contest_time = h.hour + m.minute + else + @@contest_time = nil + end + end + return @@contest_time + end + protected def self.convert_type(val,type) diff --git a/app/models/site.rb b/app/models/site.rb --- a/app/models/site.rb +++ b/app/models/site.rb @@ -10,29 +10,23 @@ end def time_left - contest_time = Configuration['contest.time_limit'] - if tmatch = /(\d+):(\d+)/.match(contest_time) - h = tmatch[1].to_i - m = tmatch[2].to_i - - contest_time = h.hour + m.minute + contest_time = Configuration.contest_time_limit - return contest_time if !self.started + return nil if contest_time == nil + + return contest_time if !self.started - current_time = Time.now.gmtime - if self.start_time!=nil - finish_time = self.start_time + contest_time - else - finish_time = current_time + contest_time - end + current_time = Time.now.gmtime + if self.start_time!=nil + finish_time = self.start_time + contest_time + else + finish_time = current_time + contest_time + end - if current_time > finish_time - return current_time - current_time - else - finish_time - current_time - end + if current_time > finish_time + return current_time - current_time else - nil + return finish_time - current_time end end @@ -41,11 +35,9 @@ return false end - contest_time = Configuration['contest.time_limit'] - if tmatch = /(\d+):(\d+)/.match(contest_time) - h = tmatch[1].to_i - m = tmatch[2].to_i - return Time.now.gmtime > (self.start_time + h.hour + m.minute) + contest_time = Configuration.contest_time_limit + if contest_time!=nil + return Time.now.gmtime > (self.start_time + contest_time) else false end diff --git a/app/models/user.rb b/app/models/user.rb --- a/app/models/user.rb +++ b/app/models/user.rb @@ -16,6 +16,8 @@ :foreign_key => "receiver_id", :order => 'created_at DESC' + has_one :contest_stat, :class_name => "UserContestStat" + belongs_to :site belongs_to :country @@ -118,6 +120,54 @@ return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 } end + # Contest information + + def contest_time_left + if Configuration.contest_mode? + return nil if site==nil + return site.time_left + elsif Configuration.indv_contest_mode? + time_limit = Configuration.contest_time_limit + if contest_stat==nil + return (Time.now.gmtime + time_limit) - Time.now.gmtime + else + finish_time = contest_stat.started_at + time_limit + current_time = Time.now.gmtime + if current_time > finish_time + return 0 + else + return finish_time - current_time + end + end + else + return nil + end + end + + def contest_finished? + if Configuration.contest_mode? + return false if site==nil + return site.finished? + elsif Configuration.indv_contest_mode? + time_limit = Configuration.contest_time_limit + + return false if contest_stat==nil + + return contest_time_left == 0 + else + return false + end + end + + def contest_started? + if Configuration.contest_mode? + return true if site==nil + return site.started + else + return true + end + end + protected def encrypt_new_password return if password.blank? diff --git a/app/models/user_contest_stat.rb b/app/models/user_contest_stat.rb new file mode 100644 --- /dev/null +++ b/app/models/user_contest_stat.rb @@ -0,0 +1,14 @@ +class UserContestStat < ActiveRecord::Base + + belongs_to :user + + def self.update_user_start_time(user) + stat = user.contest_stat + if stat == nil + stat = UserContestStat.new(:user => user, + :started_at => Time.now.gmtime) + stat.save + end + end + +end diff --git a/app/views/contests/_indv_contest_mode_index.html.haml b/app/views/contests/_indv_contest_mode_index.html.haml new file mode 100644 --- /dev/null +++ b/app/views/contests/_indv_contest_mode_index.html.haml @@ -0,0 +1,4 @@ +.submitbox + %b Menu: + = link_to '[View user start time]', :action => 'user_stat' + = link_to '[Clear all start times]', {:action => 'clear_all_stat'}, {:confirm => 'Do you really want to clear all start time statistics?'} diff --git a/app/views/contests/index.html.haml b/app/views/contests/index.html.haml new file mode 100644 --- /dev/null +++ b/app/views/contests/index.html.haml @@ -0,0 +1,9 @@ +%h1 Contest management + +- if (not Configuration.contest_mode?) and (not Configuration.indv_contest_mode?) + Currently the system is not running in contest mode. +- elsif Configuration.contest_mode? + System running in normal contest mode. +- else + System running in individual contest mode. + = render :partial => 'indv_contest_mode_index' diff --git a/app/views/contests/user_stat.html.haml b/app/views/contests/user_stat.html.haml new file mode 100644 --- /dev/null +++ b/app/views/contests/user_stat.html.haml @@ -0,0 +1,24 @@ +%h1 Individual Contest: User start time + +If you want to restart contest, you may want to +%b + = link_to '[clear all start times]', {:action => 'clear_all_stat'}, {:confirm => 'Do you really want to clear all start time statistics?'} +so that users can participate again. + +%table.info + %tr.info-head + %th Login + %th Start time + %th Time left + %th + - @users.each_with_index do |user,i| + %tr{:class => 'info-' + cycle('even','odd')} + %td= user.login + %td + - if @start_times.has_key? user.id + = @start_times[user.id] + - else + n/a + %td= format_short_duration(user.contest_time_left) + %td + = link_to '[reset]', {:action => 'clear_stat', :id => user.id}, :confirm => 'Are you sure?' diff --git a/app/views/main/list.html.haml b/app/views/main/list.html.haml --- a/app/views/main/list.html.haml +++ b/app/views/main/list.html.haml @@ -19,8 +19,7 @@ %hr/ -- if (Configuration.contest_mode) and (@user.site!=nil) | - and (@user.site.started!=true) +- if (Configuration.contest_mode?) and (@user.site!=nil) and (@user.site.started!=true) %p=t 'main.start_soon' - if Configuration.show_tasks_to?(@user) diff --git a/db/migrate/20100124040107_add_description_to_config.rb b/db/migrate/20100124040107_add_description_to_config.rb new file mode 100644 --- /dev/null +++ b/db/migrate/20100124040107_add_description_to_config.rb @@ -0,0 +1,9 @@ +class AddDescriptionToConfig < ActiveRecord::Migration + def self.up + add_column :configurations, :description, :text + end + + def self.down + remove_column :configurations, :description + end +end diff --git a/db/migrate/20100124054458_create_user_contest_stats.rb b/db/migrate/20100124054458_create_user_contest_stats.rb new file mode 100644 --- /dev/null +++ b/db/migrate/20100124054458_create_user_contest_stats.rb @@ -0,0 +1,14 @@ +class CreateUserContestStats < ActiveRecord::Migration + def self.up + create_table :user_contest_stats do |t| + t.integer :user_id + t.timestamp :started_at + + t.timestamps + end + end + + def self.down + drop_table :user_contest_stats + end +end diff --git a/db/schema.rb b/db/schema.rb --- a/db/schema.rb +++ b/db/schema.rb @@ -9,7 +9,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20100113094740) do +ActiveRecord::Schema.define(:version => 20100124054458) do create_table "announcements", :force => true do |t| t.string "author" @@ -28,6 +28,7 @@ t.string "value" t.datetime "created_at" t.datetime "updated_at" + t.text "description" end create_table "countries", :force => true do |t| @@ -129,6 +130,15 @@ t.string "password" end + create_table "submission_statuses", :force => true do |t| + t.integer "user_id" + t.integer "problem_id" + t.boolean "passed" + t.integer "submission_count" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "submissions", :force => true do |t| t.integer "user_id" t.integer "problem_id" @@ -155,12 +165,24 @@ t.datetime "updated_at" end + create_table "test_pair_assignments", :force => true do |t| + t.integer "user_id" + t.integer "problem_id" + t.integer "test_pair_id" + t.integer "test_pair_number" + t.integer "request_number" + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "submitted" + end + create_table "test_pairs", :force => true do |t| t.integer "problem_id" t.text "input" t.text "solution" t.datetime "created_at" t.datetime "updated_at" + t.integer "number" end create_table "test_requests", :force => true do |t| @@ -185,6 +207,13 @@ add_index "test_requests", ["user_id", "problem_id"], :name => "index_test_requests_on_user_id_and_problem_id" + create_table "user_contest_stats", :force => true do |t| + t.integer "user_id" + t.datetime "started_at" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "users", :force => true do |t| t.string "login", :limit => 50 t.string "full_name" diff --git a/public/stylesheets/.gitignore b/public/stylesheets/.gitignore new file mode 100644 --- /dev/null +++ b/public/stylesheets/.gitignore @@ -0,0 +1,2 @@ +.sass-cache + diff --git a/test/fixtures/user_contest_stats.yml b/test/fixtures/user_contest_stats.yml new file mode 100644 --- /dev/null +++ b/test/fixtures/user_contest_stats.yml @@ -0,0 +1,9 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +one: + user_id: 1 + started_at: 2010-01-24 12:44:58 + +two: + user_id: 1 + started_at: 2010-01-24 12:44:58 diff --git a/test/functional/contests_controller_test.rb b/test/functional/contests_controller_test.rb new file mode 100644 --- /dev/null +++ b/test/functional/contests_controller_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class ContestsControllerTest < ActionController::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end diff --git a/test/unit/helpers/contests_helper_test.rb b/test/unit/helpers/contests_helper_test.rb new file mode 100644 --- /dev/null +++ b/test/unit/helpers/contests_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class ContestsHelperTest < ActionView::TestCase +end diff --git a/test/unit/user_contest_stat_test.rb b/test/unit/user_contest_stat_test.rb new file mode 100644 --- /dev/null +++ b/test/unit/user_contest_stat_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class UserContestStatTest < ActiveSupport::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end