Description:
- after submission, redirecto to edit_submission_path - add more info to submission status - fix bug #20 and #21
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r696:0b7243db68e6 - - 11 files changed: 28 inserted, 18 deleted

@@ -1,376 +1,376
1 class MainController < ApplicationController
1 class MainController < ApplicationController
2
2
3 before_filter :authenticate, :except => [:index, :login]
3 before_filter :authenticate, :except => [:index, :login]
4 before_filter :check_viewability, :except => [:index, :login]
4 before_filter :check_viewability, :except => [:index, :login]
5
5
6 append_before_filter :confirm_and_update_start_time,
6 append_before_filter :confirm_and_update_start_time,
7 :except => [:index,
7 :except => [:index,
8 :login,
8 :login,
9 :confirm_contest_start]
9 :confirm_contest_start]
10
10
11 # to prevent log in box to be shown when user logged out of the
11 # to prevent log in box to be shown when user logged out of the
12 # system only in some tab
12 # system only in some tab
13 prepend_before_filter :reject_announcement_refresh_when_logged_out,
13 prepend_before_filter :reject_announcement_refresh_when_logged_out,
14 :only => [:announcements]
14 :only => [:announcements]
15
15
16 before_filter :authenticate_by_ip_address, :only => [:list]
16 before_filter :authenticate_by_ip_address, :only => [:list]
17
17
18 # COMMENTED OUT: filter in each action instead
18 # COMMENTED OUT: filter in each action instead
19 # before_filter :verify_time_limit, :only => [:submit]
19 # before_filter :verify_time_limit, :only => [:submit]
20
20
21 verify :method => :post, :only => [:submit],
21 verify :method => :post, :only => [:submit],
22 :redirect_to => { :action => :index }
22 :redirect_to => { :action => :index }
23
23
24 # COMMENT OUT: only need when having high load
24 # COMMENT OUT: only need when having high load
25 # caches_action :index, :login
25 # caches_action :index, :login
26
26
27 # NOTE: This method is not actually needed, 'config/routes.rb' has
27 # NOTE: This method is not actually needed, 'config/routes.rb' has
28 # assigned action login as a default action.
28 # assigned action login as a default action.
29 def index
29 def index
30 redirect_to :action => 'login'
30 redirect_to :action => 'login'
31 end
31 end
32
32
33 def login
33 def login
34 saved_notice = flash[:notice]
34 saved_notice = flash[:notice]
35 reset_session
35 reset_session
36 flash.now[:notice] = saved_notice
36 flash.now[:notice] = saved_notice
37
37
38 # EXPERIMENT:
38 # EXPERIMENT:
39 # Hide login if in single user mode and the url does not
39 # Hide login if in single user mode and the url does not
40 # explicitly specify /login
40 # explicitly specify /login
41 #
41 #
42 # logger.info "PATH: #{request.path}"
42 # logger.info "PATH: #{request.path}"
43 # if GraderConfiguration['system.single_user_mode'] and
43 # if GraderConfiguration['system.single_user_mode'] and
44 # request.path!='/main/login'
44 # request.path!='/main/login'
45 # @hidelogin = true
45 # @hidelogin = true
46 # end
46 # end
47
47
48 @announcements = Announcement.frontpage
48 @announcements = Announcement.frontpage
49 render :action => 'login', :layout => 'empty'
49 render :action => 'login', :layout => 'empty'
50 end
50 end
51
51
52 def list
52 def list
53 prepare_list_information
53 prepare_list_information
54 end
54 end
55
55
56 def help
56 def help
57 @user = User.find(session[:user_id])
57 @user = User.find(session[:user_id])
58 end
58 end
59
59
60 def submit
60 def submit
61 user = User.find(session[:user_id])
61 user = User.find(session[:user_id])
62
62
63 @submission = Submission.new
63 @submission = Submission.new
64 @submission.problem_id = params[:submission][:problem_id]
64 @submission.problem_id = params[:submission][:problem_id]
65 @submission.user = user
65 @submission.user = user
66 @submission.language_id = 0
66 @submission.language_id = 0
67 if (params['file']) and (params['file']!='')
67 if (params['file']) and (params['file']!='')
68 @submission.source = File.open(params['file'].path,'r:UTF-8',&:read)
68 @submission.source = File.open(params['file'].path,'r:UTF-8',&:read)
69 @submission.source.encode!('UTF-8','UTF-8',invalid: :replace, replace: '')
69 @submission.source.encode!('UTF-8','UTF-8',invalid: :replace, replace: '')
70 @submission.source_filename = params['file'].original_filename
70 @submission.source_filename = params['file'].original_filename
71 end
71 end
72
72
73 if (params[:editor_text])
73 if (params[:editor_text])
74 language = Language.find_by_id(params[:language_id])
74 language = Language.find_by_id(params[:language_id])
75 @submission.source = params[:editor_text]
75 @submission.source = params[:editor_text]
76 @submission.source_filename = "live_edit.#{language.ext}"
76 @submission.source_filename = "live_edit.#{language.ext}"
77 @submission.language = language
77 @submission.language = language
78 end
78 end
79
79
80 @submission.submitted_at = Time.new.gmtime
80 @submission.submitted_at = Time.new.gmtime
81 @submission.ip_address = request.remote_ip
81 @submission.ip_address = request.remote_ip
82
82
83 if GraderConfiguration.time_limit_mode? and user.contest_finished?
83 if GraderConfiguration.time_limit_mode? and user.contest_finished?
84 @submission.errors.add(:base,"The contest is over.")
84 @submission.errors.add(:base,"The contest is over.")
85 prepare_list_information
85 prepare_list_information
86 render :action => 'list' and return
86 render :action => 'list' and return
87 end
87 end
88
88
89 if @submission.valid?(@current_user)
89 if @submission.valid?(@current_user)
90 if @submission.save == false
90 if @submission.save == false
91 flash[:notice] = 'Error saving your submission'
91 flash[:notice] = 'Error saving your submission'
92 elsif Task.create(:submission_id => @submission.id,
92 elsif Task.create(:submission_id => @submission.id,
93 :status => Task::STATUS_INQUEUE) == false
93 :status => Task::STATUS_INQUEUE) == false
94 flash[:notice] = 'Error adding your submission to task queue'
94 flash[:notice] = 'Error adding your submission to task queue'
95 end
95 end
96 else
96 else
97 prepare_list_information
97 prepare_list_information
98 render :action => 'list' and return
98 render :action => 'list' and return
99 end
99 end
100 - redirect_to :action => 'list'
100 + redirect_to edit_submission_path(@submission)
101 end
101 end
102
102
103 def source
103 def source
104 submission = Submission.find(params[:id])
104 submission = Submission.find(params[:id])
105 if ((submission.user_id == session[:user_id]) and
105 if ((submission.user_id == session[:user_id]) and
106 (submission.problem != nil) and
106 (submission.problem != nil) and
107 (submission.problem.available))
107 (submission.problem.available))
108 send_data(submission.source,
108 send_data(submission.source,
109 {:filename => submission.download_filename,
109 {:filename => submission.download_filename,
110 :type => 'text/plain'})
110 :type => 'text/plain'})
111 else
111 else
112 flash[:notice] = 'Error viewing source'
112 flash[:notice] = 'Error viewing source'
113 redirect_to :action => 'list'
113 redirect_to :action => 'list'
114 end
114 end
115 end
115 end
116
116
117 def compiler_msg
117 def compiler_msg
118 @submission = Submission.find(params[:id])
118 @submission = Submission.find(params[:id])
119 if @submission.user_id == session[:user_id]
119 if @submission.user_id == session[:user_id]
120 render :action => 'compiler_msg', :layout => 'empty'
120 render :action => 'compiler_msg', :layout => 'empty'
121 else
121 else
122 flash[:notice] = 'Error viewing source'
122 flash[:notice] = 'Error viewing source'
123 redirect_to :action => 'list'
123 redirect_to :action => 'list'
124 end
124 end
125 end
125 end
126
126
127 def result
127 def result
128 if !GraderConfiguration.show_grading_result
128 if !GraderConfiguration.show_grading_result
129 redirect_to :action => 'list' and return
129 redirect_to :action => 'list' and return
130 end
130 end
131 @user = User.find(session[:user_id])
131 @user = User.find(session[:user_id])
132 @submission = Submission.find(params[:id])
132 @submission = Submission.find(params[:id])
133 if @submission.user!=@user
133 if @submission.user!=@user
134 flash[:notice] = 'You are not allowed to view result of other users.'
134 flash[:notice] = 'You are not allowed to view result of other users.'
135 redirect_to :action => 'list' and return
135 redirect_to :action => 'list' and return
136 end
136 end
137 prepare_grading_result(@submission)
137 prepare_grading_result(@submission)
138 end
138 end
139
139
140 def load_output
140 def load_output
141 if !GraderConfiguration.show_grading_result or params[:num]==nil
141 if !GraderConfiguration.show_grading_result or params[:num]==nil
142 redirect_to :action => 'list' and return
142 redirect_to :action => 'list' and return
143 end
143 end
144 @user = User.find(session[:user_id])
144 @user = User.find(session[:user_id])
145 @submission = Submission.find(params[:id])
145 @submission = Submission.find(params[:id])
146 if @submission.user!=@user
146 if @submission.user!=@user
147 flash[:notice] = 'You are not allowed to view result of other users.'
147 flash[:notice] = 'You are not allowed to view result of other users.'
148 redirect_to :action => 'list' and return
148 redirect_to :action => 'list' and return
149 end
149 end
150 case_num = params[:num].to_i
150 case_num = params[:num].to_i
151 out_filename = output_filename(@user.login,
151 out_filename = output_filename(@user.login,
152 @submission.problem.name,
152 @submission.problem.name,
153 @submission.id,
153 @submission.id,
154 case_num)
154 case_num)
155 if !FileTest.exists?(out_filename)
155 if !FileTest.exists?(out_filename)
156 flash[:notice] = 'Output not found.'
156 flash[:notice] = 'Output not found.'
157 redirect_to :action => 'list' and return
157 redirect_to :action => 'list' and return
158 end
158 end
159
159
160 if defined?(USE_APACHE_XSENDFILE) and USE_APACHE_XSENDFILE
160 if defined?(USE_APACHE_XSENDFILE) and USE_APACHE_XSENDFILE
161 response.headers['Content-Type'] = "application/force-download"
161 response.headers['Content-Type'] = "application/force-download"
162 response.headers['Content-Disposition'] = "attachment; filename=\"output-#{case_num}.txt\""
162 response.headers['Content-Disposition'] = "attachment; filename=\"output-#{case_num}.txt\""
163 response.headers["X-Sendfile"] = out_filename
163 response.headers["X-Sendfile"] = out_filename
164 response.headers['Content-length'] = File.size(out_filename)
164 response.headers['Content-length'] = File.size(out_filename)
165 render :nothing => true
165 render :nothing => true
166 else
166 else
167 send_file out_filename, :stream => false, :filename => "output-#{case_num}.txt", :type => "text/plain"
167 send_file out_filename, :stream => false, :filename => "output-#{case_num}.txt", :type => "text/plain"
168 end
168 end
169 end
169 end
170
170
171 def error
171 def error
172 @user = User.find(session[:user_id])
172 @user = User.find(session[:user_id])
173 end
173 end
174
174
175 # announcement refreshing and hiding methods
175 # announcement refreshing and hiding methods
176
176
177 def announcements
177 def announcements
178 if params.has_key? 'recent'
178 if params.has_key? 'recent'
179 prepare_announcements(params[:recent])
179 prepare_announcements(params[:recent])
180 else
180 else
181 prepare_announcements
181 prepare_announcements
182 end
182 end
183 render(:partial => 'announcement',
183 render(:partial => 'announcement',
184 :collection => @announcements,
184 :collection => @announcements,
185 :locals => {:announcement_effect => true})
185 :locals => {:announcement_effect => true})
186 end
186 end
187
187
188 def confirm_contest_start
188 def confirm_contest_start
189 user = User.find(session[:user_id])
189 user = User.find(session[:user_id])
190 if request.method == 'POST'
190 if request.method == 'POST'
191 user.update_start_time
191 user.update_start_time
192 redirect_to :action => 'list'
192 redirect_to :action => 'list'
193 else
193 else
194 @contests = user.contests
194 @contests = user.contests
195 @user = user
195 @user = user
196 end
196 end
197 end
197 end
198
198
199 protected
199 protected
200
200
201 def prepare_announcements(recent=nil)
201 def prepare_announcements(recent=nil)
202 if GraderConfiguration.show_tasks_to?(@user)
202 if GraderConfiguration.show_tasks_to?(@user)
203 @announcements = Announcement.published(true)
203 @announcements = Announcement.published(true)
204 else
204 else
205 @announcements = Announcement.published
205 @announcements = Announcement.published
206 end
206 end
207 if recent!=nil
207 if recent!=nil
208 recent_id = recent.to_i
208 recent_id = recent.to_i
209 @announcements = @announcements.find_all { |a| a.id > recent_id }
209 @announcements = @announcements.find_all { |a| a.id > recent_id }
210 end
210 end
211 end
211 end
212
212
213 def prepare_list_information
213 def prepare_list_information
214 @user = User.find(session[:user_id])
214 @user = User.find(session[:user_id])
215 if not GraderConfiguration.multicontests?
215 if not GraderConfiguration.multicontests?
216 @problems = @user.available_problems
216 @problems = @user.available_problems
217 else
217 else
218 @contest_problems = @user.available_problems_group_by_contests
218 @contest_problems = @user.available_problems_group_by_contests
219 @problems = @user.available_problems
219 @problems = @user.available_problems
220 end
220 end
221 @prob_submissions = {}
221 @prob_submissions = {}
222 @problems.each do |p|
222 @problems.each do |p|
223 sub = Submission.find_last_by_user_and_problem(@user.id,p.id)
223 sub = Submission.find_last_by_user_and_problem(@user.id,p.id)
224 if sub!=nil
224 if sub!=nil
225 @prob_submissions[p.id] = { :count => sub.number, :submission => sub }
225 @prob_submissions[p.id] = { :count => sub.number, :submission => sub }
226 else
226 else
227 @prob_submissions[p.id] = { :count => 0, :submission => nil }
227 @prob_submissions[p.id] = { :count => 0, :submission => nil }
228 end
228 end
229 end
229 end
230 prepare_announcements
230 prepare_announcements
231 end
231 end
232
232
233 def check_viewability
233 def check_viewability
234 @user = User.find(session[:user_id])
234 @user = User.find(session[:user_id])
235 if (!GraderConfiguration.show_tasks_to?(@user)) and
235 if (!GraderConfiguration.show_tasks_to?(@user)) and
236 ((action_name=='submission') or (action_name=='submit'))
236 ((action_name=='submission') or (action_name=='submit'))
237 redirect_to :action => 'list' and return
237 redirect_to :action => 'list' and return
238 end
238 end
239 end
239 end
240
240
241 def prepare_grading_result(submission)
241 def prepare_grading_result(submission)
242 if GraderConfiguration.task_grading_info.has_key? submission.problem.name
242 if GraderConfiguration.task_grading_info.has_key? submission.problem.name
243 grading_info = GraderConfiguration.task_grading_info[submission.problem.name]
243 grading_info = GraderConfiguration.task_grading_info[submission.problem.name]
244 else
244 else
245 # guess task info from problem.full_score
245 # guess task info from problem.full_score
246 cases = submission.problem.full_score / 10
246 cases = submission.problem.full_score / 10
247 grading_info = {
247 grading_info = {
248 'testruns' => cases,
248 'testruns' => cases,
249 'testcases' => cases
249 'testcases' => cases
250 }
250 }
251 end
251 end
252 @test_runs = []
252 @test_runs = []
253 if grading_info['testruns'].is_a? Integer
253 if grading_info['testruns'].is_a? Integer
254 trun_count = grading_info['testruns']
254 trun_count = grading_info['testruns']
255 trun_count.times do |i|
255 trun_count.times do |i|
256 @test_runs << [ read_grading_result(@user.login,
256 @test_runs << [ read_grading_result(@user.login,
257 submission.problem.name,
257 submission.problem.name,
258 submission.id,
258 submission.id,
259 i+1) ]
259 i+1) ]
260 end
260 end
261 else
261 else
262 grading_info['testruns'].keys.sort.each do |num|
262 grading_info['testruns'].keys.sort.each do |num|
263 run = []
263 run = []
264 testrun = grading_info['testruns'][num]
264 testrun = grading_info['testruns'][num]
265 testrun.each do |c|
265 testrun.each do |c|
266 run << read_grading_result(@user.login,
266 run << read_grading_result(@user.login,
267 submission.problem.name,
267 submission.problem.name,
268 submission.id,
268 submission.id,
269 c)
269 c)
270 end
270 end
271 @test_runs << run
271 @test_runs << run
272 end
272 end
273 end
273 end
274 end
274 end
275
275
276 def grading_result_dir(user_name, problem_name, submission_id, case_num)
276 def grading_result_dir(user_name, problem_name, submission_id, case_num)
277 return "#{GRADING_RESULT_DIR}/#{user_name}/#{problem_name}/#{submission_id}/test-result/#{case_num}"
277 return "#{GRADING_RESULT_DIR}/#{user_name}/#{problem_name}/#{submission_id}/test-result/#{case_num}"
278 end
278 end
279
279
280 def output_filename(user_name, problem_name, submission_id, case_num)
280 def output_filename(user_name, problem_name, submission_id, case_num)
281 dir = grading_result_dir(user_name,problem_name, submission_id, case_num)
281 dir = grading_result_dir(user_name,problem_name, submission_id, case_num)
282 return "#{dir}/output.txt"
282 return "#{dir}/output.txt"
283 end
283 end
284
284
285 def read_grading_result(user_name, problem_name, submission_id, case_num)
285 def read_grading_result(user_name, problem_name, submission_id, case_num)
286 dir = grading_result_dir(user_name,problem_name, submission_id, case_num)
286 dir = grading_result_dir(user_name,problem_name, submission_id, case_num)
287 result_file_name = "#{dir}/result"
287 result_file_name = "#{dir}/result"
288 if !FileTest.exists?(result_file_name)
288 if !FileTest.exists?(result_file_name)
289 return {:num => case_num, :msg => 'program did not run'}
289 return {:num => case_num, :msg => 'program did not run'}
290 else
290 else
291 results = File.open(result_file_name).readlines
291 results = File.open(result_file_name).readlines
292 run_stat = extract_running_stat(results)
292 run_stat = extract_running_stat(results)
293 output_filename = "#{dir}/output.txt"
293 output_filename = "#{dir}/output.txt"
294 if FileTest.exists?(output_filename)
294 if FileTest.exists?(output_filename)
295 output_file = true
295 output_file = true
296 output_size = File.size(output_filename)
296 output_size = File.size(output_filename)
297 else
297 else
298 output_file = false
298 output_file = false
299 output_size = 0
299 output_size = 0
300 end
300 end
301
301
302 return {
302 return {
303 :num => case_num,
303 :num => case_num,
304 :msg => results[0],
304 :msg => results[0],
305 :run_stat => run_stat,
305 :run_stat => run_stat,
306 :output => output_file,
306 :output => output_file,
307 :output_size => output_size
307 :output_size => output_size
308 }
308 }
309 end
309 end
310 end
310 end
311
311
312 # copied from grader/script/lib/test_request_helper.rb
312 # copied from grader/script/lib/test_request_helper.rb
313 def extract_running_stat(results)
313 def extract_running_stat(results)
314 running_stat_line = results[-1]
314 running_stat_line = results[-1]
315
315
316 # extract exit status line
316 # extract exit status line
317 run_stat = ""
317 run_stat = ""
318 if !(/[Cc]orrect/.match(results[0]))
318 if !(/[Cc]orrect/.match(results[0]))
319 run_stat = results[0].chomp
319 run_stat = results[0].chomp
320 else
320 else
321 run_stat = 'Program exited normally'
321 run_stat = 'Program exited normally'
322 end
322 end
323
323
324 logger.info "Stat line: #{running_stat_line}"
324 logger.info "Stat line: #{running_stat_line}"
325
325
326 # extract running time
326 # extract running time
327 if res = /r(.*)u(.*)s/.match(running_stat_line)
327 if res = /r(.*)u(.*)s/.match(running_stat_line)
328 seconds = (res[1].to_f + res[2].to_f)
328 seconds = (res[1].to_f + res[2].to_f)
329 time_stat = "Time used: #{seconds} sec."
329 time_stat = "Time used: #{seconds} sec."
330 else
330 else
331 seconds = nil
331 seconds = nil
332 time_stat = "Time used: n/a sec."
332 time_stat = "Time used: n/a sec."
333 end
333 end
334
334
335 # extract memory usage
335 # extract memory usage
336 if res = /s(.*)m/.match(running_stat_line)
336 if res = /s(.*)m/.match(running_stat_line)
337 memory_used = res[1].to_i
337 memory_used = res[1].to_i
338 else
338 else
339 memory_used = -1
339 memory_used = -1
340 end
340 end
341
341
342 return {
342 return {
343 :msg => "#{run_stat}\n#{time_stat}",
343 :msg => "#{run_stat}\n#{time_stat}",
344 :running_time => seconds,
344 :running_time => seconds,
345 :exit_status => run_stat,
345 :exit_status => run_stat,
346 :memory_usage => memory_used
346 :memory_usage => memory_used
347 }
347 }
348 end
348 end
349
349
350 def confirm_and_update_start_time
350 def confirm_and_update_start_time
351 user = User.find(session[:user_id])
351 user = User.find(session[:user_id])
352 if (GraderConfiguration.indv_contest_mode? and
352 if (GraderConfiguration.indv_contest_mode? and
353 GraderConfiguration['contest.confirm_indv_contest_start'] and
353 GraderConfiguration['contest.confirm_indv_contest_start'] and
354 !user.contest_started?)
354 !user.contest_started?)
355 redirect_to :action => 'confirm_contest_start' and return
355 redirect_to :action => 'confirm_contest_start' and return
356 end
356 end
357 if not GraderConfiguration.analysis_mode?
357 if not GraderConfiguration.analysis_mode?
358 user.update_start_time
358 user.update_start_time
359 end
359 end
360 end
360 end
361
361
362 def reject_announcement_refresh_when_logged_out
362 def reject_announcement_refresh_when_logged_out
363 if not session[:user_id]
363 if not session[:user_id]
364 render :text => 'Access forbidden', :status => 403
364 render :text => 'Access forbidden', :status => 403
365 end
365 end
366
366
367 if GraderConfiguration.multicontests?
367 if GraderConfiguration.multicontests?
368 user = User.find(session[:user_id])
368 user = User.find(session[:user_id])
369 if user.contest_stat.forced_logout
369 if user.contest_stat.forced_logout
370 render :text => 'Access forbidden', :status => 403
370 render :text => 'Access forbidden', :status => 403
371 end
371 end
372 end
372 end
373 end
373 end
374
374
375 end
375 end
376
376
@@ -1,519 +1,521
1 require 'csv'
1 require 'csv'
2
2
3 class ReportController < ApplicationController
3 class ReportController < ApplicationController
4
4
5 before_filter :authenticate
5 before_filter :authenticate
6
6
7 before_filter :admin_authorization, only: [:login_stat,:submission_stat, :stuck, :cheat_report, :cheat_scruntinize, :show_max_score]
7 before_filter :admin_authorization, only: [:login_stat,:submission_stat, :stuck, :cheat_report, :cheat_scruntinize, :show_max_score]
8
8
9 before_filter(only: [:problem_hof]) { |c|
9 before_filter(only: [:problem_hof]) { |c|
10 return false unless authenticate
10 return false unless authenticate
11
11
12 admin_authorization unless GraderConfiguration["right.user_view_submission"]
12 admin_authorization unless GraderConfiguration["right.user_view_submission"]
13 }
13 }
14
14
15 def max_score
15 def max_score
16 end
16 end
17
17
18 def current_score
18 def current_score
19 @problems = Problem.available_problems
19 @problems = Problem.available_problems
20 @users = User.includes(:contests).includes(:contest_stat).where(enabled: true)
20 @users = User.includes(:contests).includes(:contest_stat).where(enabled: true)
21 @scorearray = calculate_max_score(@problems, @users,0,0,true)
21 @scorearray = calculate_max_score(@problems, @users,0,0,true)
22
22
23 #rencer accordingly
23 #rencer accordingly
24 if params[:button] == 'download' then
24 if params[:button] == 'download' then
25 csv = gen_csv_from_scorearray(@scorearray,@problems)
25 csv = gen_csv_from_scorearray(@scorearray,@problems)
26 send_data csv, filename: 'max_score.csv'
26 send_data csv, filename: 'max_score.csv'
27 else
27 else
28 #render template: 'user_admin/user_stat'
28 #render template: 'user_admin/user_stat'
29 render 'current_score'
29 render 'current_score'
30 end
30 end
31 end
31 end
32
32
33 def show_max_score
33 def show_max_score
34 #process parameters
34 #process parameters
35 #problems
35 #problems
36 @problems = []
36 @problems = []
37 if params[:problem_id]
37 if params[:problem_id]
38 params[:problem_id].each do |id|
38 params[:problem_id].each do |id|
39 next unless id.strip != ""
39 next unless id.strip != ""
40 pid = Problem.find_by_id(id.to_i)
40 pid = Problem.find_by_id(id.to_i)
41 @problems << pid if pid
41 @problems << pid if pid
42 end
42 end
43 end
43 end
44
44
45 #users
45 #users
46 @users = if params[:user] == "all" then
46 @users = if params[:user] == "all" then
47 User.includes(:contests).includes(:contest_stat)
47 User.includes(:contests).includes(:contest_stat)
48 else
48 else
49 User.includes(:contests).includes(:contest_stat).where(enabled: true)
49 User.includes(:contests).includes(:contest_stat).where(enabled: true)
50 end
50 end
51
51
52 #set up range from param
52 #set up range from param
53 @since_id = params.fetch(:from_id, 0).to_i
53 @since_id = params.fetch(:from_id, 0).to_i
54 @until_id = params.fetch(:to_id, 0).to_i
54 @until_id = params.fetch(:to_id, 0).to_i
55 + @since_id = nil if @since_id == 0
56 + @until_id = nil if @until_id == 0
55
57
56 #calculate the routine
58 #calculate the routine
57 @scorearray = calculate_max_score(@problems, @users, @since_id, @until_id)
59 @scorearray = calculate_max_score(@problems, @users, @since_id, @until_id)
58
60
59 #rencer accordingly
61 #rencer accordingly
60 if params[:button] == 'download' then
62 if params[:button] == 'download' then
61 csv = gen_csv_from_scorearray(@scorearray,@problems)
63 csv = gen_csv_from_scorearray(@scorearray,@problems)
62 send_data csv, filename: 'max_score.csv'
64 send_data csv, filename: 'max_score.csv'
63 else
65 else
64 #render template: 'user_admin/user_stat'
66 #render template: 'user_admin/user_stat'
65 render 'max_score'
67 render 'max_score'
66 end
68 end
67
69
68 end
70 end
69
71
70 def score
72 def score
71 if params[:commit] == 'download csv'
73 if params[:commit] == 'download csv'
72 @problems = Problem.all
74 @problems = Problem.all
73 else
75 else
74 @problems = Problem.available_problems
76 @problems = Problem.available_problems
75 end
77 end
76 @users = User.includes(:contests, :contest_stat).where(enabled: true)
78 @users = User.includes(:contests, :contest_stat).where(enabled: true)
77 @scorearray = Array.new
79 @scorearray = Array.new
78 @users.each do |u|
80 @users.each do |u|
79 ustat = Array.new
81 ustat = Array.new
80 ustat[0] = u
82 ustat[0] = u
81 @problems.each do |p|
83 @problems.each do |p|
82 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
84 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
83 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
85 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
84 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
86 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
85 else
87 else
86 ustat << [0,false]
88 ustat << [0,false]
87 end
89 end
88 end
90 end
89 @scorearray << ustat
91 @scorearray << ustat
90 end
92 end
91 if params[:commit] == 'download csv' then
93 if params[:commit] == 'download csv' then
92 csv = gen_csv_from_scorearray(@scorearray,@problems)
94 csv = gen_csv_from_scorearray(@scorearray,@problems)
93 send_data csv, filename: 'last_score.csv'
95 send_data csv, filename: 'last_score.csv'
94 else
96 else
95 render template: 'user_admin/user_stat'
97 render template: 'user_admin/user_stat'
96 end
98 end
97
99
98 end
100 end
99
101
100 def login_stat
102 def login_stat
101 @logins = Array.new
103 @logins = Array.new
102
104
103 date_and_time = '%Y-%m-%d %H:%M'
105 date_and_time = '%Y-%m-%d %H:%M'
104 begin
106 begin
105 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
107 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
106 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
108 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
107 rescue
109 rescue
108 @since_time = DateTime.new(1000,1,1)
110 @since_time = DateTime.new(1000,1,1)
109 end
111 end
110 begin
112 begin
111 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
113 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
112 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
114 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
113 rescue
115 rescue
114 @until_time = DateTime.new(3000,1,1)
116 @until_time = DateTime.new(3000,1,1)
115 end
117 end
116
118
117 User.all.each do |user|
119 User.all.each do |user|
118 @logins << { id: user.id,
120 @logins << { id: user.id,
119 login: user.login,
121 login: user.login,
120 full_name: user.full_name,
122 full_name: user.full_name,
121 count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
123 count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
122 user.id,@since_time,@until_time)
124 user.id,@since_time,@until_time)
123 .count(:id),
125 .count(:id),
124 min: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
126 min: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
125 user.id,@since_time,@until_time)
127 user.id,@since_time,@until_time)
126 .minimum(:created_at),
128 .minimum(:created_at),
127 max: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
129 max: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
128 user.id,@since_time,@until_time)
130 user.id,@since_time,@until_time)
129 .maximum(:created_at),
131 .maximum(:created_at),
130 ip: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
132 ip: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
131 user.id,@since_time,@until_time)
133 user.id,@since_time,@until_time)
132 .select(:ip_address).uniq
134 .select(:ip_address).uniq
133
135
134 }
136 }
135 end
137 end
136 end
138 end
137
139
138 def submission_stat
140 def submission_stat
139
141
140 date_and_time = '%Y-%m-%d %H:%M'
142 date_and_time = '%Y-%m-%d %H:%M'
141 begin
143 begin
142 @since_time = DateTime.strptime(params[:since_datetime],date_and_time)
144 @since_time = DateTime.strptime(params[:since_datetime],date_and_time)
143 rescue
145 rescue
144 @since_time = DateTime.new(1000,1,1)
146 @since_time = DateTime.new(1000,1,1)
145 end
147 end
146 begin
148 begin
147 @until_time = DateTime.strptime(params[:until_datetime],date_and_time)
149 @until_time = DateTime.strptime(params[:until_datetime],date_and_time)
148 rescue
150 rescue
149 @until_time = DateTime.new(3000,1,1)
151 @until_time = DateTime.new(3000,1,1)
150 end
152 end
151
153
152 @submissions = {}
154 @submissions = {}
153
155
154 User.find_each do |user|
156 User.find_each do |user|
155 @submissions[user.id] = { login: user.login, full_name: user.full_name, count: 0, sub: { } }
157 @submissions[user.id] = { login: user.login, full_name: user.full_name, count: 0, sub: { } }
156 end
158 end
157
159
158 Submission.where("submitted_at >= ? AND submitted_at <= ?",@since_time,@until_time).find_each do |s|
160 Submission.where("submitted_at >= ? AND submitted_at <= ?",@since_time,@until_time).find_each do |s|
159 if @submissions[s.user_id]
161 if @submissions[s.user_id]
160 if not @submissions[s.user_id][:sub].has_key?(s.problem_id)
162 if not @submissions[s.user_id][:sub].has_key?(s.problem_id)
161 a = Problem.find_by_id(s.problem_id)
163 a = Problem.find_by_id(s.problem_id)
162 @submissions[s.user_id][:sub][s.problem_id] =
164 @submissions[s.user_id][:sub][s.problem_id] =
163 { prob_name: (a ? a.full_name : '(NULL)'),
165 { prob_name: (a ? a.full_name : '(NULL)'),
164 sub_ids: [s.id] }
166 sub_ids: [s.id] }
165 else
167 else
166 @submissions[s.user_id][:sub][s.problem_id][:sub_ids] << s.id
168 @submissions[s.user_id][:sub][s.problem_id][:sub_ids] << s.id
167 end
169 end
168 @submissions[s.user_id][:count] += 1
170 @submissions[s.user_id][:count] += 1
169 end
171 end
170 end
172 end
171 end
173 end
172
174
173 def problem_hof
175 def problem_hof
174 # gen problem list
176 # gen problem list
175 @user = User.find(session[:user_id])
177 @user = User.find(session[:user_id])
176 @problems = @user.available_problems
178 @problems = @user.available_problems
177
179
178 # get selected problems or the default
180 # get selected problems or the default
179 if params[:id]
181 if params[:id]
180 begin
182 begin
181 @problem = Problem.available.find(params[:id])
183 @problem = Problem.available.find(params[:id])
182 rescue
184 rescue
183 redirect_to action: :problem_hof
185 redirect_to action: :problem_hof
184 flash[:notice] = 'Error: submissions for that problem are not viewable.'
186 flash[:notice] = 'Error: submissions for that problem are not viewable.'
185 return
187 return
186 end
188 end
187 end
189 end
188
190
189 return unless @problem
191 return unless @problem
190
192
191 @by_lang = {} #aggregrate by language
193 @by_lang = {} #aggregrate by language
192
194
193 range =65
195 range =65
194 @histogram = { data: Array.new(range,0), summary: {} }
196 @histogram = { data: Array.new(range,0), summary: {} }
195 @summary = {count: 0, solve: 0, attempt: 0}
197 @summary = {count: 0, solve: 0, attempt: 0}
196 user = Hash.new(0)
198 user = Hash.new(0)
197 Submission.where(problem_id: @problem.id).find_each do |sub|
199 Submission.where(problem_id: @problem.id).find_each do |sub|
198 #histogram
200 #histogram
199 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
201 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
200 @histogram[:data][d.to_i] += 1 if d < range
202 @histogram[:data][d.to_i] += 1 if d < range
201
203
202 next unless sub.points
204 next unless sub.points
203 @summary[:count] += 1
205 @summary[:count] += 1
204 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
206 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
205
207
206 lang = Language.find_by_id(sub.language_id)
208 lang = Language.find_by_id(sub.language_id)
207 next unless lang
209 next unless lang
208 next unless sub.points >= @problem.full_score
210 next unless sub.points >= @problem.full_score
209
211
210 #initialize
212 #initialize
211 unless @by_lang.has_key?(lang.pretty_name)
213 unless @by_lang.has_key?(lang.pretty_name)
212 @by_lang[lang.pretty_name] = {
214 @by_lang[lang.pretty_name] = {
213 runtime: { avail: false, value: 2**30-1 },
215 runtime: { avail: false, value: 2**30-1 },
214 memory: { avail: false, value: 2**30-1 },
216 memory: { avail: false, value: 2**30-1 },
215 length: { avail: false, value: 2**30-1 },
217 length: { avail: false, value: 2**30-1 },
216 first: { avail: false, value: DateTime.new(3000,1,1) }
218 first: { avail: false, value: DateTime.new(3000,1,1) }
217 }
219 }
218 end
220 end
219
221
220 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
222 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
221 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
223 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
222 end
224 end
223
225
224 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
226 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
225 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
227 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
226 end
228 end
227
229
228 if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and sub.user and
230 if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and sub.user and
229 !sub.user.admin?
231 !sub.user.admin?
230 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
232 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
231 end
233 end
232
234
233 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
235 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
234 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
236 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
235 end
237 end
236 end
238 end
237
239
238 #process user_id
240 #process user_id
239 @by_lang.each do |lang,prop|
241 @by_lang.each do |lang,prop|
240 prop.each do |k,v|
242 prop.each do |k,v|
241 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
243 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
242 end
244 end
243 end
245 end
244
246
245 #sum into best
247 #sum into best
246 if @by_lang and @by_lang.first
248 if @by_lang and @by_lang.first
247 @best = @by_lang.first[1].clone
249 @best = @by_lang.first[1].clone
248 @by_lang.each do |lang,prop|
250 @by_lang.each do |lang,prop|
249 if @best[:runtime][:value] >= prop[:runtime][:value]
251 if @best[:runtime][:value] >= prop[:runtime][:value]
250 @best[:runtime] = prop[:runtime]
252 @best[:runtime] = prop[:runtime]
251 @best[:runtime][:lang] = lang
253 @best[:runtime][:lang] = lang
252 end
254 end
253 if @best[:memory][:value] >= prop[:memory][:value]
255 if @best[:memory][:value] >= prop[:memory][:value]
254 @best[:memory] = prop[:memory]
256 @best[:memory] = prop[:memory]
255 @best[:memory][:lang] = lang
257 @best[:memory][:lang] = lang
256 end
258 end
257 if @best[:length][:value] >= prop[:length][:value]
259 if @best[:length][:value] >= prop[:length][:value]
258 @best[:length] = prop[:length]
260 @best[:length] = prop[:length]
259 @best[:length][:lang] = lang
261 @best[:length][:lang] = lang
260 end
262 end
261 if @best[:first][:value] >= prop[:first][:value]
263 if @best[:first][:value] >= prop[:first][:value]
262 @best[:first] = prop[:first]
264 @best[:first] = prop[:first]
263 @best[:first][:lang] = lang
265 @best[:first][:lang] = lang
264 end
266 end
265 end
267 end
266 end
268 end
267
269
268 @histogram[:summary][:max] = [@histogram[:data].max,1].max
270 @histogram[:summary][:max] = [@histogram[:data].max,1].max
269 @summary[:attempt] = user.count
271 @summary[:attempt] = user.count
270 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
272 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
271 end
273 end
272
274
273 def stuck #report struggling user,problem
275 def stuck #report struggling user,problem
274 # init
276 # init
275 user,problem = nil
277 user,problem = nil
276 solve = true
278 solve = true
277 tries = 0
279 tries = 0
278 @struggle = Array.new
280 @struggle = Array.new
279 record = {}
281 record = {}
280 Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
282 Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
281 next unless sub.problem and sub.user
283 next unless sub.problem and sub.user
282 if user != sub.user_id or problem != sub.problem_id
284 if user != sub.user_id or problem != sub.problem_id
283 @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
285 @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
284 record = {user: sub.user, problem: sub.problem}
286 record = {user: sub.user, problem: sub.problem}
285 user,problem = sub.user_id, sub.problem_id
287 user,problem = sub.user_id, sub.problem_id
286 solve = false
288 solve = false
287 tries = 0
289 tries = 0
288 end
290 end
289 if sub.points >= sub.problem.full_score
291 if sub.points >= sub.problem.full_score
290 solve = true
292 solve = true
291 else
293 else
292 tries += 1
294 tries += 1
293 end
295 end
294 end
296 end
295 @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
297 @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
296 @struggle = @struggle[0..50]
298 @struggle = @struggle[0..50]
297 end
299 end
298
300
299
301
300 def multiple_login
302 def multiple_login
301 #user with multiple IP
303 #user with multiple IP
302 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:login)
304 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:login)
303 last,count = 0,0
305 last,count = 0,0
304 first = 0
306 first = 0
305 @users = []
307 @users = []
306 raw.each do |r|
308 raw.each do |r|
307 if last != r.user.login
309 if last != r.user.login
308 count = 1
310 count = 1
309 last = r.user.login
311 last = r.user.login
310 first = r
312 first = r
311 else
313 else
312 @users << first if count == 1
314 @users << first if count == 1
313 @users << r
315 @users << r
314 count += 1
316 count += 1
315 end
317 end
316 end
318 end
317
319
318 #IP with multiple user
320 #IP with multiple user
319 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:ip_address)
321 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:ip_address)
320 last,count = 0,0
322 last,count = 0,0
321 first = 0
323 first = 0
322 @ip = []
324 @ip = []
323 raw.each do |r|
325 raw.each do |r|
324 if last != r.ip_address
326 if last != r.ip_address
325 count = 1
327 count = 1
326 last = r.ip_address
328 last = r.ip_address
327 first = r
329 first = r
328 else
330 else
329 @ip << first if count == 1
331 @ip << first if count == 1
330 @ip << r
332 @ip << r
331 count += 1
333 count += 1
332 end
334 end
333 end
335 end
334 end
336 end
335
337
336 def cheat_report
338 def cheat_report
337 date_and_time = '%Y-%m-%d %H:%M'
339 date_and_time = '%Y-%m-%d %H:%M'
338 begin
340 begin
339 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
341 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
340 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
342 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
341 rescue
343 rescue
342 @since_time = Time.zone.now.ago( 90.minutes)
344 @since_time = Time.zone.now.ago( 90.minutes)
343 end
345 end
344 begin
346 begin
345 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
347 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
346 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
348 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
347 rescue
349 rescue
348 @until_time = Time.zone.now
350 @until_time = Time.zone.now
349 end
351 end
350
352
351 #multi login
353 #multi login
352 @ml = Login.joins(:user).where("logins.created_at >= ? and logins.created_at <= ?",@since_time,@until_time).select('users.login,count(distinct ip_address) as count,users.full_name').group("users.id").having("count > 1")
354 @ml = Login.joins(:user).where("logins.created_at >= ? and logins.created_at <= ?",@since_time,@until_time).select('users.login,count(distinct ip_address) as count,users.full_name').group("users.id").having("count > 1")
353
355
354 st = <<-SQL
356 st = <<-SQL
355 SELECT l2.*
357 SELECT l2.*
356 FROM logins l2 INNER JOIN
358 FROM logins l2 INNER JOIN
357 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
359 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
358 FROM logins l
360 FROM logins l
359 INNER JOIN users u ON l.user_id = u.id
361 INNER JOIN users u ON l.user_id = u.id
360 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
362 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
361 GROUP BY u.id
363 GROUP BY u.id
362 HAVING count > 1
364 HAVING count > 1
363 ) ml ON l2.user_id = ml.id
365 ) ml ON l2.user_id = ml.id
364 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
366 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
365 UNION
367 UNION
366 SELECT l2.*
368 SELECT l2.*
367 FROM logins l2 INNER JOIN
369 FROM logins l2 INNER JOIN
368 (SELECT l.ip_address,COUNT(DISTINCT u.id) as count
370 (SELECT l.ip_address,COUNT(DISTINCT u.id) as count
369 FROM logins l
371 FROM logins l
370 INNER JOIN users u ON l.user_id = u.id
372 INNER JOIN users u ON l.user_id = u.id
371 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
373 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
372 GROUP BY l.ip_address
374 GROUP BY l.ip_address
373 HAVING count > 1
375 HAVING count > 1
374 ) ml on ml.ip_address = l2.ip_address
376 ) ml on ml.ip_address = l2.ip_address
375 INNER JOIN users u ON l2.user_id = u.id
377 INNER JOIN users u ON l2.user_id = u.id
376 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
378 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
377 ORDER BY ip_address,created_at
379 ORDER BY ip_address,created_at
378 SQL
380 SQL
379 @mld = Login.find_by_sql(st)
381 @mld = Login.find_by_sql(st)
380
382
381 st = <<-SQL
383 st = <<-SQL
382 SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
384 SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
383 FROM submissions s INNER JOIN
385 FROM submissions s INNER JOIN
384 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
386 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
385 FROM logins l
387 FROM logins l
386 INNER JOIN users u ON l.user_id = u.id
388 INNER JOIN users u ON l.user_id = u.id
387 WHERE l.created_at >= ? and l.created_at <= ?
389 WHERE l.created_at >= ? and l.created_at <= ?
388 GROUP BY u.id
390 GROUP BY u.id
389 HAVING count > 1
391 HAVING count > 1
390 ) ml ON s.user_id = ml.id
392 ) ml ON s.user_id = ml.id
391 WHERE s.submitted_at >= ? and s.submitted_at <= ?
393 WHERE s.submitted_at >= ? and s.submitted_at <= ?
392 UNION
394 UNION
393 SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
395 SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
394 FROM submissions s INNER JOIN
396 FROM submissions s INNER JOIN
395 (SELECT l.ip_address,COUNT(DISTINCT u.id) as count
397 (SELECT l.ip_address,COUNT(DISTINCT u.id) as count
396 FROM logins l
398 FROM logins l
397 INNER JOIN users u ON l.user_id = u.id
399 INNER JOIN users u ON l.user_id = u.id
398 WHERE l.created_at >= ? and l.created_at <= ?
400 WHERE l.created_at >= ? and l.created_at <= ?
399 GROUP BY l.ip_address
401 GROUP BY l.ip_address
400 HAVING count > 1
402 HAVING count > 1
401 ) ml on ml.ip_address = s.ip_address
403 ) ml on ml.ip_address = s.ip_address
402 WHERE s.submitted_at >= ? and s.submitted_at <= ?
404 WHERE s.submitted_at >= ? and s.submitted_at <= ?
403 ORDER BY ip_address,submitted_at
405 ORDER BY ip_address,submitted_at
404 SQL
406 SQL
405 @subs = Submission.joins(:problem).find_by_sql([st,@since_time,@until_time,
407 @subs = Submission.joins(:problem).find_by_sql([st,@since_time,@until_time,
406 @since_time,@until_time,
408 @since_time,@until_time,
407 @since_time,@until_time,
409 @since_time,@until_time,
408 @since_time,@until_time])
410 @since_time,@until_time])
409
411
410 end
412 end
411
413
412 def cheat_scruntinize
414 def cheat_scruntinize
413 #convert date & time
415 #convert date & time
414 date_and_time = '%Y-%m-%d %H:%M'
416 date_and_time = '%Y-%m-%d %H:%M'
415 begin
417 begin
416 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
418 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
417 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
419 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
418 rescue
420 rescue
419 @since_time = Time.zone.now.ago( 90.minutes)
421 @since_time = Time.zone.now.ago( 90.minutes)
420 end
422 end
421 begin
423 begin
422 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
424 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
423 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
425 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
424 rescue
426 rescue
425 @until_time = Time.zone.now
427 @until_time = Time.zone.now
426 end
428 end
427
429
428 #convert sid
430 #convert sid
429 @sid = params[:SID].split(/[,\s]/) if params[:SID]
431 @sid = params[:SID].split(/[,\s]/) if params[:SID]
430 unless @sid and @sid.size > 0
432 unless @sid and @sid.size > 0
431 return
433 return
432 redirect_to actoin: :cheat_scruntinize
434 redirect_to actoin: :cheat_scruntinize
433 flash[:notice] = 'Please enter at least 1 student id'
435 flash[:notice] = 'Please enter at least 1 student id'
434 end
436 end
435 mark = Array.new(@sid.size,'?')
437 mark = Array.new(@sid.size,'?')
436 condition = "(u.login = " + mark.join(' OR u.login = ') + ')'
438 condition = "(u.login = " + mark.join(' OR u.login = ') + ')'
437
439
438 @st = <<-SQL
440 @st = <<-SQL
439 SELECT l.created_at as submitted_at ,-1 as id,u.login,u.full_name,l.ip_address,"" as problem_id,"" as points,l.user_id
441 SELECT l.created_at as submitted_at ,-1 as id,u.login,u.full_name,l.ip_address,"" as problem_id,"" as points,l.user_id
440 FROM logins l INNER JOIN users u on l.user_id = u.id
442 FROM logins l INNER JOIN users u on l.user_id = u.id
441 WHERE l.created_at >= ? AND l.created_at <= ? AND #{condition}
443 WHERE l.created_at >= ? AND l.created_at <= ? AND #{condition}
442 UNION
444 UNION
443 SELECT s.submitted_at,s.id,u.login,u.full_name,s.ip_address,s.problem_id,s.points,s.user_id
445 SELECT s.submitted_at,s.id,u.login,u.full_name,s.ip_address,s.problem_id,s.points,s.user_id
444 FROM submissions s INNER JOIN users u ON s.user_id = u.id
446 FROM submissions s INNER JOIN users u ON s.user_id = u.id
445 WHERE s.submitted_at >= ? AND s.submitted_at <= ? AND #{condition}
447 WHERE s.submitted_at >= ? AND s.submitted_at <= ? AND #{condition}
446 ORDER BY submitted_at
448 ORDER BY submitted_at
447 SQL
449 SQL
448
450
449 p = [@st,@since_time,@until_time] + @sid + [@since_time,@until_time] + @sid
451 p = [@st,@since_time,@until_time] + @sid + [@since_time,@until_time] + @sid
450 @logs = Submission.joins(:problem).find_by_sql(p)
452 @logs = Submission.joins(:problem).find_by_sql(p)
451
453
452
454
453
455
454
456
455
457
456 end
458 end
457
459
458 protected
460 protected
459
461
460 def calculate_max_score(problems, users,since_id,until_id, get_last_score = false)
462 def calculate_max_score(problems, users,since_id,until_id, get_last_score = false)
461 scorearray = Array.new
463 scorearray = Array.new
462 users.each do |u|
464 users.each do |u|
463 ustat = Array.new
465 ustat = Array.new
464 ustat[0] = u
466 ustat[0] = u
465 problems.each do |p|
467 problems.each do |p|
466 unless get_last_score
468 unless get_last_score
467 #get max score
469 #get max score
468 max_points = 0
470 max_points = 0
469 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
471 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
470 max_points = sub.points if sub and sub.points and (sub.points > max_points)
472 max_points = sub.points if sub and sub.points and (sub.points > max_points)
471 end
473 end
472 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
474 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
473 else
475 else
474 #get latest score
476 #get latest score
475 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
477 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
476 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
478 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
477 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
479 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
478 else
480 else
479 ustat << [0,false]
481 ustat << [0,false]
480 end
482 end
481 end
483 end
482 end
484 end
483 scorearray << ustat
485 scorearray << ustat
484 end
486 end
485 return scorearray
487 return scorearray
486 end
488 end
487
489
488 def gen_csv_from_scorearray(scorearray,problem)
490 def gen_csv_from_scorearray(scorearray,problem)
489 CSV.generate do |csv|
491 CSV.generate do |csv|
490 #add header
492 #add header
491 header = ['User','Name', 'Activated?', 'Logged in', 'Contest']
493 header = ['User','Name', 'Activated?', 'Logged in', 'Contest']
492 problem.each { |p| header << p.name }
494 problem.each { |p| header << p.name }
493 header += ['Total','Passed']
495 header += ['Total','Passed']
494 csv << header
496 csv << header
495 #add data
497 #add data
496 scorearray.each do |sc|
498 scorearray.each do |sc|
497 total = num_passed = 0
499 total = num_passed = 0
498 row = Array.new
500 row = Array.new
499 sc.each_index do |i|
501 sc.each_index do |i|
500 if i == 0
502 if i == 0
501 row << sc[i].login
503 row << sc[i].login
502 row << sc[i].full_name
504 row << sc[i].full_name
503 row << sc[i].activated
505 row << sc[i].activated
504 row << (sc[i].try(:contest_stat).try(:started_at)!=nil ? 'yes' : 'no')
506 row << (sc[i].try(:contest_stat).try(:started_at)!=nil ? 'yes' : 'no')
505 row << sc[i].contests.collect {|c| c.name}.join(', ')
507 row << sc[i].contests.collect {|c| c.name}.join(', ')
506 else
508 else
507 row << sc[i][0]
509 row << sc[i][0]
508 total += sc[i][0]
510 total += sc[i][0]
509 num_passed += 1 if sc[i][1]
511 num_passed += 1 if sc[i][1]
510 end
512 end
511 end
513 end
512 row << total
514 row << total
513 row << num_passed
515 row << num_passed
514 csv << row
516 csv << row
515 end
517 end
516 end
518 end
517 end
519 end
518
520
519 end
521 end
@@ -1,221 +1,224
1 # Methods added to this helper will be available to all templates in the application.
1 # Methods added to this helper will be available to all templates in the application.
2 module ApplicationHelper
2 module ApplicationHelper
3
3
4 #new bootstrap header
4 #new bootstrap header
5 def navbar_user_header
5 def navbar_user_header
6 left_menu = ''
6 left_menu = ''
7 right_menu = ''
7 right_menu = ''
8 user = User.find(session[:user_id])
8 user = User.find(session[:user_id])
9
9
10 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
10 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
11 left_menu << add_menu("#{I18n.t 'menu.tasks'}", 'tasks', 'list')
11 left_menu << add_menu("#{I18n.t 'menu.tasks'}", 'tasks', 'list')
12 left_menu << add_menu("#{I18n.t 'menu.submissions'}", 'main', 'submission')
12 left_menu << add_menu("#{I18n.t 'menu.submissions'}", 'main', 'submission')
13 left_menu << add_menu("#{I18n.t 'menu.test'}", 'test', 'index')
13 left_menu << add_menu("#{I18n.t 'menu.test'}", 'test', 'index')
14 end
14 end
15
15
16 if GraderConfiguration['right.user_hall_of_fame']
16 if GraderConfiguration['right.user_hall_of_fame']
17 left_menu << add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof')
17 left_menu << add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof')
18 end
18 end
19
19
20 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help')
20 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help')
21 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'list', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}})
21 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'list', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}})
22 if GraderConfiguration['system.user_setting_enabled']
22 if GraderConfiguration['system.user_setting_enabled']
23 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-cog')}".html_safe, 'users', 'index', {title: I18n.t('menu.settings'), data: {toggle: 'tooltip'}})
23 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-cog')}".html_safe, 'users', 'index', {title: I18n.t('menu.settings'), data: {toggle: 'tooltip'}})
24 end
24 end
25 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-log-out')} #{user.full_name}".html_safe, 'main', 'login', {title: I18n.t('menu.log_out'), data: {toggle: 'tooltip'}})
25 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-log-out')} #{user.full_name}".html_safe, 'main', 'login', {title: I18n.t('menu.log_out'), data: {toggle: 'tooltip'}})
26
26
27
27
28 result = content_tag(:ul,left_menu.html_safe,class: 'nav navbar-nav') + content_tag(:ul,right_menu.html_safe,class: 'nav navbar-nav navbar-right')
28 result = content_tag(:ul,left_menu.html_safe,class: 'nav navbar-nav') + content_tag(:ul,right_menu.html_safe,class: 'nav navbar-nav navbar-right')
29 end
29 end
30
30
31 def add_menu(title, controller, action, html_option = {})
31 def add_menu(title, controller, action, html_option = {})
32 link_option = {controller: controller, action: action}
32 link_option = {controller: controller, action: action}
33 html_option[:class] = (html_option[:class] || '') + " active" if current_page?(link_option)
33 html_option[:class] = (html_option[:class] || '') + " active" if current_page?(link_option)
34 content_tag(:li, link_to(title,link_option),html_option)
34 content_tag(:li, link_to(title,link_option),html_option)
35 end
35 end
36
36
37 def user_header
37 def user_header
38 menu_items = ''
38 menu_items = ''
39 user = User.find(session[:user_id])
39 user = User.find(session[:user_id])
40
40
41 if (user!=nil) and (session[:admin])
41 if (user!=nil) and (session[:admin])
42 # admin menu
42 # admin menu
43 menu_items << "<b>Administrative task:</b> "
43 menu_items << "<b>Administrative task:</b> "
44 append_to menu_items, '[Announcements]', 'announcements', 'index'
44 append_to menu_items, '[Announcements]', 'announcements', 'index'
45 append_to menu_items, '[Msg console]', 'messages', 'console'
45 append_to menu_items, '[Msg console]', 'messages', 'console'
46 append_to menu_items, '[Problems]', 'problems', 'index'
46 append_to menu_items, '[Problems]', 'problems', 'index'
47 append_to menu_items, '[Users]', 'user_admin', 'index'
47 append_to menu_items, '[Users]', 'user_admin', 'index'
48 append_to menu_items, '[Results]', 'user_admin', 'user_stat'
48 append_to menu_items, '[Results]', 'user_admin', 'user_stat'
49 append_to menu_items, '[Report]', 'report', 'multiple_login'
49 append_to menu_items, '[Report]', 'report', 'multiple_login'
50 append_to menu_items, '[Graders]', 'graders', 'list'
50 append_to menu_items, '[Graders]', 'graders', 'list'
51 append_to menu_items, '[Contests]', 'contest_management', 'index'
51 append_to menu_items, '[Contests]', 'contest_management', 'index'
52 append_to menu_items, '[Sites]', 'sites', 'index'
52 append_to menu_items, '[Sites]', 'sites', 'index'
53 append_to menu_items, '[System config]', 'configurations', 'index'
53 append_to menu_items, '[System config]', 'configurations', 'index'
54 menu_items << "<br/>"
54 menu_items << "<br/>"
55 end
55 end
56
56
57 # main page
57 # main page
58 append_to menu_items, "[#{I18n.t 'menu.main'}]", 'main', 'list'
58 append_to menu_items, "[#{I18n.t 'menu.main'}]", 'main', 'list'
59 append_to menu_items, "[#{I18n.t 'menu.messages'}]", 'messages', 'list'
59 append_to menu_items, "[#{I18n.t 'menu.messages'}]", 'messages', 'list'
60
60
61 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
61 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
62 append_to menu_items, "[#{I18n.t 'menu.tasks'}]", 'tasks', 'list'
62 append_to menu_items, "[#{I18n.t 'menu.tasks'}]", 'tasks', 'list'
63 append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission'
63 append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission'
64 append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index'
64 append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index'
65 end
65 end
66
66
67 if GraderConfiguration['right.user_hall_of_fame']
67 if GraderConfiguration['right.user_hall_of_fame']
68 append_to menu_items, "[#{I18n.t 'menu.hall_of_fame'}]", 'report', 'problem_hof'
68 append_to menu_items, "[#{I18n.t 'menu.hall_of_fame'}]", 'report', 'problem_hof'
69 end
69 end
70 append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help'
70 append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help'
71
71
72 if GraderConfiguration['system.user_setting_enabled']
72 if GraderConfiguration['system.user_setting_enabled']
73 append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index'
73 append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index'
74 end
74 end
75 append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login'
75 append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login'
76
76
77 menu_items.html_safe
77 menu_items.html_safe
78 end
78 end
79
79
80 def append_to(option,label, controller, action)
80 def append_to(option,label, controller, action)
81 option << ' ' if option!=''
81 option << ' ' if option!=''
82 option << link_to_unless_current(label,
82 option << link_to_unless_current(label,
83 :controller => controller,
83 :controller => controller,
84 :action => action)
84 :action => action)
85 end
85 end
86
86
87 def format_short_time(time)
87 def format_short_time(time)
88 - now = Time.now.gmtime
88 + now = Time.zone.now
89 st = ''
89 st = ''
90 - if (time.yday != now.yday) or
90 + if (time.yday != now.yday) or (time.year != now.year)
91 - (time.year != now.year)
91 + st = time.strftime("%d/%m/%y ")
92 - st = time.strftime("%x ")
93 end
92 end
94 st + time.strftime("%X")
93 st + time.strftime("%X")
95 end
94 end
96
95
97 def format_short_duration(duration)
96 def format_short_duration(duration)
98 return '' if duration==nil
97 return '' if duration==nil
99 d = duration.to_f
98 d = duration.to_f
100 return Time.at(d).gmtime.strftime("%X")
99 return Time.at(d).gmtime.strftime("%X")
101 end
100 end
102
101
102 + def format_full_time_ago(time)
103 + st = time_ago_in_words(time) + ' ago (' + format_short_time(time) + ')'
104 + end
105 +
103 def read_textfile(fname,max_size=2048)
106 def read_textfile(fname,max_size=2048)
104 begin
107 begin
105 File.open(fname).read(max_size)
108 File.open(fname).read(max_size)
106 rescue
109 rescue
107 nil
110 nil
108 end
111 end
109 end
112 end
110
113
111 def toggle_button(on,toggle_url,id, option={})
114 def toggle_button(on,toggle_url,id, option={})
112 btn_size = option[:size] || 'btn-xs'
115 btn_size = option[:size] || 'btn-xs'
113 link_to (on ? "Yes" : "No"), toggle_url,
116 link_to (on ? "Yes" : "No"), toggle_url,
114 {class: "btn btn-block #{btn_size} btn-#{on ? 'success' : 'default'} ajax-toggle",
117 {class: "btn btn-block #{btn_size} btn-#{on ? 'success' : 'default'} ajax-toggle",
115 id: id,
118 id: id,
116 data: {remote: true, method: 'get'}}
119 data: {remote: true, method: 'get'}}
117 end
120 end
118
121
119 def get_ace_mode(language)
122 def get_ace_mode(language)
120 # return ace mode string from Language
123 # return ace mode string from Language
121
124
122 case language.pretty_name
125 case language.pretty_name
123 when 'Pascal'
126 when 'Pascal'
124 'ace/mode/pascal'
127 'ace/mode/pascal'
125 when 'C++','C'
128 when 'C++','C'
126 'ace/mode/c_cpp'
129 'ace/mode/c_cpp'
127 when 'Ruby'
130 when 'Ruby'
128 'ace/mode/ruby'
131 'ace/mode/ruby'
129 when 'Python'
132 when 'Python'
130 'ace/mode/python'
133 'ace/mode/python'
131 when 'Java'
134 when 'Java'
132 'ace/mode/java'
135 'ace/mode/java'
133 else
136 else
134 'ace/mode/c_cpp'
137 'ace/mode/c_cpp'
135 end
138 end
136 end
139 end
137
140
138
141
139 def user_title_bar(user)
142 def user_title_bar(user)
140 header = ''
143 header = ''
141 time_left = ''
144 time_left = ''
142
145
143 #
146 #
144 # if the contest is over
147 # if the contest is over
145 if GraderConfiguration.time_limit_mode?
148 if GraderConfiguration.time_limit_mode?
146 if user.contest_finished?
149 if user.contest_finished?
147 header = <<CONTEST_OVER
150 header = <<CONTEST_OVER
148 <tr><td colspan="2" align="center">
151 <tr><td colspan="2" align="center">
149 <span class="contest-over-msg">THE CONTEST IS OVER</span>
152 <span class="contest-over-msg">THE CONTEST IS OVER</span>
150 </td></tr>
153 </td></tr>
151 CONTEST_OVER
154 CONTEST_OVER
152 end
155 end
153 if !user.contest_started?
156 if !user.contest_started?
154 time_left = "&nbsp;&nbsp;" + (t 'title_bar.contest_not_started')
157 time_left = "&nbsp;&nbsp;" + (t 'title_bar.contest_not_started')
155 else
158 else
156 time_left = "&nbsp;&nbsp;" + (t 'title_bar.remaining_time') +
159 time_left = "&nbsp;&nbsp;" + (t 'title_bar.remaining_time') +
157 " #{format_short_duration(user.contest_time_left)}"
160 " #{format_short_duration(user.contest_time_left)}"
158 end
161 end
159 end
162 end
160
163
161 #
164 #
162 # if the contest is in the anaysis mode
165 # if the contest is in the anaysis mode
163 if GraderConfiguration.analysis_mode?
166 if GraderConfiguration.analysis_mode?
164 header = <<ANALYSISMODE
167 header = <<ANALYSISMODE
165 <tr><td colspan="2" align="center">
168 <tr><td colspan="2" align="center">
166 <span class="contest-over-msg">ANALYSIS MODE</span>
169 <span class="contest-over-msg">ANALYSIS MODE</span>
167 </td></tr>
170 </td></tr>
168 ANALYSISMODE
171 ANALYSISMODE
169 end
172 end
170
173
171 contest_name = GraderConfiguration['contest.name']
174 contest_name = GraderConfiguration['contest.name']
172
175
173 #
176 #
174 # build real title bar
177 # build real title bar
175 result = <<TITLEBAR
178 result = <<TITLEBAR
176 <div class="title">
179 <div class="title">
177 <table>
180 <table>
178 #{header}
181 #{header}
179 <tr>
182 <tr>
180 <td class="left-col">
183 <td class="left-col">
181 #{user.full_name}<br/>
184 #{user.full_name}<br/>
182 #{t 'title_bar.current_time'} #{format_short_time(Time.zone.now)}
185 #{t 'title_bar.current_time'} #{format_short_time(Time.zone.now)}
183 #{time_left}
186 #{time_left}
184 <br/>
187 <br/>
185 </td>
188 </td>
186 <td class="right-col">#{contest_name}</td>
189 <td class="right-col">#{contest_name}</td>
187 </tr>
190 </tr>
188 </table>
191 </table>
189 </div>
192 </div>
190 TITLEBAR
193 TITLEBAR
191 result.html_safe
194 result.html_safe
192 end
195 end
193
196
194 def markdown(text)
197 def markdown(text)
195 markdown = RDiscount.new(text)
198 markdown = RDiscount.new(text)
196 markdown.to_html.html_safe
199 markdown.to_html.html_safe
197 end
200 end
198
201
199
202
200 BOOTSTRAP_FLASH_MSG = {
203 BOOTSTRAP_FLASH_MSG = {
201 success: 'alert-success',
204 success: 'alert-success',
202 error: 'alert-danger',
205 error: 'alert-danger',
203 alert: 'alert-danger',
206 alert: 'alert-danger',
204 notice: 'alert-info'
207 notice: 'alert-info'
205 }
208 }
206
209
207 def bootstrap_class_for(flash_type)
210 def bootstrap_class_for(flash_type)
208 BOOTSTRAP_FLASH_MSG.fetch(flash_type.to_sym, flash_type.to_s)
211 BOOTSTRAP_FLASH_MSG.fetch(flash_type.to_sym, flash_type.to_s)
209 end
212 end
210
213
211 def flash_messages
214 def flash_messages
212 flash.each do |msg_type, message|
215 flash.each do |msg_type, message|
213 concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)} fade in") do
216 concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)} fade in") do
214 concat content_tag(:button, 'x', class: "close", data: { dismiss: 'alert' })
217 concat content_tag(:button, 'x', class: "close", data: { dismiss: 'alert' })
215 concat message
218 concat message
216 end)
219 end)
217 end
220 end
218 nil
221 nil
219 end
222 end
220
223
221 end
224 end
@@ -1,166 +1,166
1 class Submission < ActiveRecord::Base
1 class Submission < ActiveRecord::Base
2
2
3 belongs_to :language
3 belongs_to :language
4 belongs_to :problem
4 belongs_to :problem
5 belongs_to :user
5 belongs_to :user
6
6
7 before_validation :assign_problem
7 before_validation :assign_problem
8 before_validation :assign_language
8 before_validation :assign_language
9
9
10 validates_presence_of :source
10 validates_presence_of :source
11 validates_length_of :source, :maximum => 100_000, :allow_blank => true, :message => 'too long'
11 validates_length_of :source, :maximum => 100_000, :allow_blank => true, :message => 'too long'
12 validates_length_of :source, :minimum => 1, :allow_blank => true, :message => 'too short'
12 validates_length_of :source, :minimum => 1, :allow_blank => true, :message => 'too short'
13 validate :must_have_valid_problem
13 validate :must_have_valid_problem
14 validate :must_specify_language
14 validate :must_specify_language
15
15
16 has_one :task
16 has_one :task
17
17
18 before_save :assign_latest_number_if_new_recond
18 before_save :assign_latest_number_if_new_recond
19
19
20 def self.find_last_by_user_and_problem(user_id, problem_id)
20 def self.find_last_by_user_and_problem(user_id, problem_id)
21 where("user_id = ? AND problem_id = ?",user_id,problem_id).last
21 where("user_id = ? AND problem_id = ?",user_id,problem_id).last
22 end
22 end
23
23
24 def self.find_all_last_by_problem(problem_id)
24 def self.find_all_last_by_problem(problem_id)
25 # need to put in SQL command, maybe there's a better way
25 # need to put in SQL command, maybe there's a better way
26 Submission.includes(:user).find_by_sql("SELECT * FROM submissions " +
26 Submission.includes(:user).find_by_sql("SELECT * FROM submissions " +
27 "WHERE id = " +
27 "WHERE id = " +
28 "(SELECT MAX(id) FROM submissions AS subs " +
28 "(SELECT MAX(id) FROM submissions AS subs " +
29 "WHERE subs.user_id = submissions.user_id AND " +
29 "WHERE subs.user_id = submissions.user_id AND " +
30 "problem_id = " + problem_id.to_s + " " +
30 "problem_id = " + problem_id.to_s + " " +
31 "GROUP BY user_id) " +
31 "GROUP BY user_id) " +
32 "ORDER BY user_id")
32 "ORDER BY user_id")
33 end
33 end
34
34
35 def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id)
35 def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id)
36 records = Submission.where(problem_id: problem_id,user_id: user_id)
36 records = Submission.where(problem_id: problem_id,user_id: user_id)
37 - records = records.where('id >= ?',since_id) if since_id > 0
37 + records = records.where('id >= ?',since_id) if since_id and since_id > 0
38 - records = records.where('id <= ?',until_id) if until_id > 0
38 + records = records.where('id <= ?',until_id) if until_id and until_id > 0
39 records.all
39 records.all
40 end
40 end
41
41
42 def self.find_last_for_all_available_problems(user_id)
42 def self.find_last_for_all_available_problems(user_id)
43 submissions = Array.new
43 submissions = Array.new
44 problems = Problem.available_problems
44 problems = Problem.available_problems
45 problems.each do |problem|
45 problems.each do |problem|
46 sub = Submission.find_last_by_user_and_problem(user_id, problem.id)
46 sub = Submission.find_last_by_user_and_problem(user_id, problem.id)
47 submissions << sub if sub!=nil
47 submissions << sub if sub!=nil
48 end
48 end
49 submissions
49 submissions
50 end
50 end
51
51
52 def self.find_by_user_problem_number(user_id, problem_id, number)
52 def self.find_by_user_problem_number(user_id, problem_id, number)
53 where("user_id = ? AND problem_id = ? AND number = ?",user_id,problem_id,number).first
53 where("user_id = ? AND problem_id = ? AND number = ?",user_id,problem_id,number).first
54 end
54 end
55
55
56 def self.find_all_by_user_problem(user_id, problem_id)
56 def self.find_all_by_user_problem(user_id, problem_id)
57 where("user_id = ? AND problem_id = ?",user_id,problem_id)
57 where("user_id = ? AND problem_id = ?",user_id,problem_id)
58 end
58 end
59
59
60 def download_filename
60 def download_filename
61 if self.problem.output_only
61 if self.problem.output_only
62 return self.source_filename
62 return self.source_filename
63 else
63 else
64 timestamp = self.submitted_at.localtime.strftime("%H%M%S")
64 timestamp = self.submitted_at.localtime.strftime("%H%M%S")
65 return "#{self.problem.name}-#{timestamp}.#{self.language.ext}"
65 return "#{self.problem.name}-#{timestamp}.#{self.language.ext}"
66 end
66 end
67 end
67 end
68
68
69 protected
69 protected
70
70
71 def self.find_option_in_source(option, source)
71 def self.find_option_in_source(option, source)
72 if source==nil
72 if source==nil
73 return nil
73 return nil
74 end
74 end
75 i = 0
75 i = 0
76 source.each_line do |s|
76 source.each_line do |s|
77 if s =~ option
77 if s =~ option
78 words = s.split
78 words = s.split
79 return words[1]
79 return words[1]
80 end
80 end
81 i = i + 1
81 i = i + 1
82 if i==10
82 if i==10
83 return nil
83 return nil
84 end
84 end
85 end
85 end
86 return nil
86 return nil
87 end
87 end
88
88
89 def self.find_language_in_source(source, source_filename="")
89 def self.find_language_in_source(source, source_filename="")
90 langopt = find_option_in_source(/^LANG:/,source)
90 langopt = find_option_in_source(/^LANG:/,source)
91 if langopt
91 if langopt
92 return (Language.find_by_name(langopt) ||
92 return (Language.find_by_name(langopt) ||
93 Language.find_by_pretty_name(langopt))
93 Language.find_by_pretty_name(langopt))
94 else
94 else
95 if source_filename
95 if source_filename
96 return Language.find_by_extension(source_filename.split('.').last)
96 return Language.find_by_extension(source_filename.split('.').last)
97 else
97 else
98 return nil
98 return nil
99 end
99 end
100 end
100 end
101 end
101 end
102
102
103 def self.find_problem_in_source(source, source_filename="")
103 def self.find_problem_in_source(source, source_filename="")
104 prob_opt = find_option_in_source(/^TASK:/,source)
104 prob_opt = find_option_in_source(/^TASK:/,source)
105 if problem = Problem.find_by_name(prob_opt)
105 if problem = Problem.find_by_name(prob_opt)
106 return problem
106 return problem
107 else
107 else
108 if source_filename
108 if source_filename
109 return Problem.find_by_name(source_filename.split('.').first)
109 return Problem.find_by_name(source_filename.split('.').first)
110 else
110 else
111 return nil
111 return nil
112 end
112 end
113 end
113 end
114 end
114 end
115
115
116 def assign_problem
116 def assign_problem
117 if self.problem_id!=-1
117 if self.problem_id!=-1
118 begin
118 begin
119 self.problem = Problem.find(self.problem_id)
119 self.problem = Problem.find(self.problem_id)
120 rescue ActiveRecord::RecordNotFound
120 rescue ActiveRecord::RecordNotFound
121 self.problem = nil
121 self.problem = nil
122 end
122 end
123 else
123 else
124 self.problem = Submission.find_problem_in_source(self.source,
124 self.problem = Submission.find_problem_in_source(self.source,
125 self.source_filename)
125 self.source_filename)
126 end
126 end
127 end
127 end
128
128
129 def assign_language
129 def assign_language
130 self.language = Submission.find_language_in_source(self.source,
130 self.language = Submission.find_language_in_source(self.source,
131 self.source_filename)
131 self.source_filename)
132 end
132 end
133
133
134 # validation codes
134 # validation codes
135 def must_specify_language
135 def must_specify_language
136 return if self.source==nil
136 return if self.source==nil
137
137
138 # for output_only tasks
138 # for output_only tasks
139 return if self.problem!=nil and self.problem.output_only
139 return if self.problem!=nil and self.problem.output_only
140
140
141 if self.language==nil
141 if self.language==nil
142 errors.add('source',"Cannot detect language. Did you submit a correct source file?") unless self.language!=nil
142 errors.add('source',"Cannot detect language. Did you submit a correct source file?") unless self.language!=nil
143 end
143 end
144 end
144 end
145
145
146 def must_have_valid_problem
146 def must_have_valid_problem
147 return if self.source==nil
147 return if self.source==nil
148 if self.problem==nil
148 if self.problem==nil
149 errors.add('problem',"must be specified.")
149 errors.add('problem',"must be specified.")
150 else
150 else
151 #admin always have right
151 #admin always have right
152 return if self.user.admin?
152 return if self.user.admin?
153
153
154 #check if user has the right to submit the problem
154 #check if user has the right to submit the problem
155 errors.add('problem',"must be valid.") if (!self.user.available_problems.include?(self.problem)) and (self.new_record?)
155 errors.add('problem',"must be valid.") if (!self.user.available_problems.include?(self.problem)) and (self.new_record?)
156 end
156 end
157 end
157 end
158
158
159 # callbacks
159 # callbacks
160 def assign_latest_number_if_new_recond
160 def assign_latest_number_if_new_recond
161 return if !self.new_record?
161 return if !self.new_record?
162 latest = Submission.find_last_by_user_and_problem(self.user_id, self.problem_id)
162 latest = Submission.find_last_by_user_and_problem(self.user_id, self.problem_id)
163 self.number = (latest==nil) ? 1 : latest.number + 1;
163 self.number = (latest==nil) ? 1 : latest.number + 1;
164 end
164 end
165
165
166 end
166 end
@@ -1,26 +1,28
1 -
2 - if submission.nil?
1 - if submission.nil?
3 = "-"
2 = "-"
4 - else
3 - else
4 + %strong= "Submission ID:"
5 + = submission.id
6 + %br
5 - unless submission.graded_at
7 - unless submission.graded_at
6 - = t 'main.submitted_at'
8 + %strong= t 'main.submitted_at:'
7 - = format_short_time(submission.submitted_at.localtime)
9 + = format_full_time_ago(submission.submitted_at.localtime)
8 - else
10 - else
9 - %strong= t 'main.graded_at'
11 + %strong= t 'main.graded_at:'
10 - = "#{format_short_time(submission.graded_at.localtime)} "
12 + = format_full_time_ago(submission.graded_at.localtime)
11 %br
13 %br
12 - if GraderConfiguration['ui.show_score']
14 - if GraderConfiguration['ui.show_score']
13 %strong=t 'main.score'
15 %strong=t 'main.score'
14 = "#{(submission.points*100/submission.problem.full_score).to_i} "
16 = "#{(submission.points*100/submission.problem.full_score).to_i} "
15 = " ["
17 = " ["
16 %tt
18 %tt
17 = submission.grader_comment
19 = submission.grader_comment
18 = "]"
20 = "]"
19 %br
21 %br
20 %strong View:
22 %strong View:
21 - if GraderConfiguration.show_grading_result
23 - if GraderConfiguration.show_grading_result
22 = link_to '[detailed result]', :action => 'result', :id => submission.id
24 = link_to '[detailed result]', :action => 'result', :id => submission.id
23 - = link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'}
25 + = link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'} if submission.graded_at
24 = link_to "#{t 'main.src_link'}", download_submission_path(submission.id), class: 'btn btn-xs btn-info'
26 = link_to "#{t 'main.src_link'}", download_submission_path(submission.id), class: 'btn btn-xs btn-info'
25 = link_to "#{t 'main.submissions_link'}", problem_submissions_path(problem_id), class: 'btn btn-xs btn-info'
27 = link_to "#{t 'main.submissions_link'}", problem_submissions_path(problem_id), class: 'btn btn-xs btn-info'
26
28
@@ -1,49 +1,49
1 %h1 Maximum score
1 %h1 Maximum score
2
2
3 = form_tag report_show_max_score_path
3 = form_tag report_show_max_score_path
4 .row
4 .row
5 .col-md-4
5 .col-md-4
6 .panel.panel-primary
6 .panel.panel-primary
7 .panel-heading
7 .panel-heading
8 Problems
8 Problems
9 .panel-body
9 .panel-body
10 %p
10 %p
11 Select problem(s) that we wish to know the score.
11 Select problem(s) that we wish to know the score.
12 = label_tag :problem_id, "Problems"
12 = label_tag :problem_id, "Problems"
13 = select_tag 'problem_id[]',
13 = select_tag 'problem_id[]',
14 options_for_select(Problem.all.collect {|p| ["[#{p.name}] #{p.full_name}", p.id]},params[:problem_id]),
14 options_for_select(Problem.all.collect {|p| ["[#{p.name}] #{p.full_name}", p.id]},params[:problem_id]),
15 { class: 'select2 form-control', multiple: "true" }
15 { class: 'select2 form-control', multiple: "true" }
16 .col-md-4
16 .col-md-4
17 .panel.panel-primary
17 .panel.panel-primary
18 .panel-heading
18 .panel-heading
19 Submission range
19 Submission range
20 .panel-body
20 .panel-body
21 %p
21 %p
22 Input minimum and maximum range of submission ID that should be included. A blank value for min and max means -1 and infinity, respectively.
22 Input minimum and maximum range of submission ID that should be included. A blank value for min and max means -1 and infinity, respectively.
23 .form-group
23 .form-group
24 = label_tag :from, "Min"
24 = label_tag :from, "Min"
25 = text_field_tag 'from_id', @since_id, class: "form-control"
25 = text_field_tag 'from_id', @since_id, class: "form-control"
26 .form-group
26 .form-group
27 = label_tag :from, "Max"
27 = label_tag :from, "Max"
28 = text_field_tag 'to_id', @until_id, class: "form-control"
28 = text_field_tag 'to_id', @until_id, class: "form-control"
29 .col-md-4
29 .col-md-4
30 .panel.panel-primary
30 .panel.panel-primary
31 .panel-heading
31 .panel-heading
32 Users
32 Users
33 .panel-body
33 .panel-body
34 .radio
34 .radio
35 %label
35 %label
36 - = radio_button_tag 'users', 'all', true
36 + = radio_button_tag 'users', 'all', (params[:users] == "all")
37 All users
37 All users
38 .radio
38 .radio
39 %label
39 %label
40 - = radio_button_tag 'users', 'enabled'
40 + = radio_button_tag 'users', 'enabled', (params[:users] == "enabled")
41 Only enabled users
41 Only enabled users
42 .row
42 .row
43 .col-md-12
43 .col-md-12
44 = button_tag 'Show', class: "btn btn-primary btn-large", value: "show"
44 = button_tag 'Show', class: "btn btn-primary btn-large", value: "show"
45 = button_tag 'Download CSV', class: "btn btn-primary btn-large", value: "download"
45 = button_tag 'Download CSV', class: "btn btn-primary btn-large", value: "download"
46
46
47 - if @scorearray
47 - if @scorearray
48 %h2 Result
48 %h2 Result
49 =render "score_table"
49 =render "score_table"
@@ -1,271 +1,271
1 %h2 Live submit
1 %h2 Live submit
2 %br
2 %br
3
3
4 %textarea#text_sourcecode{style: "display:none"}~ @source
4 %textarea#text_sourcecode{style: "display:none"}~ @source
5 .container
5 .container
6 .row
6 .row
7 .col-md-12
7 .col-md-12
8 .alert.alert-info
8 .alert.alert-info
9 Write your code in the following box, choose language, and click submit button when finished
9 Write your code in the following box, choose language, and click submit button when finished
10 .row
10 .row
11 .col-md-8
11 .col-md-8
12 %div#editor{style: 'height: 500px; border-radius: 7px; font-size: 14px;'}
12 %div#editor{style: 'height: 500px; border-radius: 7px; font-size: 14px;'}
13 .col-md-4
13 .col-md-4
14 - # submission form
14 - # submission form
15 = form_tag({controller: :main, :action => 'submit'}, :multipart => true, class: 'form') do
15 = form_tag({controller: :main, :action => 'submit'}, :multipart => true, class: 'form') do
16
16
17 = hidden_field_tag 'editor_text', @source
17 = hidden_field_tag 'editor_text', @source
18 = hidden_field_tag 'submission[problem_id]', @problem.id
18 = hidden_field_tag 'submission[problem_id]', @problem.id
19 .form-group
19 .form-group
20 = label_tag "Task:"
20 = label_tag "Task:"
21 = text_field_tag 'asdf', "#{@problem.long_name}", class: 'form-control', disabled: true
21 = text_field_tag 'asdf', "#{@problem.long_name}", class: 'form-control', disabled: true
22
22
23 .form-group
23 .form-group
24 = label_tag 'Language'
24 = label_tag 'Language'
25 = select_tag 'language_id', options_from_collection_for_select(Language.all, 'id', 'pretty_name', @lang_id || Language.find_by_pretty_name("Python").id || Language.first.id), class: 'form-control select', style: "width: 100px"
25 = select_tag 'language_id', options_from_collection_for_select(Language.all, 'id', 'pretty_name', @lang_id || Language.find_by_pretty_name("Python").id || Language.first.id), class: 'form-control select', style: "width: 100px"
26 .form-group
26 .form-group
27 = submit_tag 'Submit', class: 'btn btn-success', id: 'live_submit',
27 = submit_tag 'Submit', class: 'btn btn-success', id: 'live_submit',
28 data: {confirm: "Submitting this source code for task #{@problem.long_name}?"}
28 data: {confirm: "Submitting this source code for task #{@problem.long_name}?"}
29 - # latest submission status
29 - # latest submission status
30 - .panel.panel-info
30 + .panel{class: (@submission && @submission.graded_at) ? "panel-info" : "panel-warning"}
31 .panel-heading
31 .panel-heading
32 Latest Submission Status
32 Latest Submission Status
33 = link_to "Refresh",get_latest_submission_status_submissions_path(@submission.user,@problem), class: "btn btn-default btn-sm", remote: true if @submission
33 = link_to "Refresh",get_latest_submission_status_submissions_path(@submission.user,@problem), class: "btn btn-default btn-sm", remote: true if @submission
34 .panel-body
34 .panel-body
35 - if @submission
35 - if @submission
36 = render :partial => 'submission_short',
36 = render :partial => 'submission_short',
37 :locals => {submission: @submission, problem_name: @problem.name, problem_id: @problem.id }
37 :locals => {submission: @submission, problem_name: @problem.name, problem_id: @problem.id }
38 .row
38 .row
39 .col-md-12
39 .col-md-12
40 %h2 Console
40 %h2 Console
41 %textarea#console{style: 'height: 100%; width: 100%;background-color:#000;color:#fff;font-family: consolas, monaco, "Droid Sans Mono";',rows: 20}
41 %textarea#console{style: 'height: 100%; width: 100%;background-color:#000;color:#fff;font-family: consolas, monaco, "Droid Sans Mono";',rows: 20}
42
42
43 :javascript
43 :javascript
44 $(document).ready(function() {
44 $(document).ready(function() {
45 e = ace.edit("editor")
45 e = ace.edit("editor")
46 e.setValue($("#text_sourcecode").val());
46 e.setValue($("#text_sourcecode").val());
47 e.gotoLine(1);
47 e.gotoLine(1);
48 $("#language_id").trigger('change');
48 $("#language_id").trigger('change');
49 brython();
49 brython();
50 });
50 });
51
51
52
52
53 %script#__main__{type:'text/python3'}
53 %script#__main__{type:'text/python3'}
54 :plain
54 :plain
55 import sys
55 import sys
56 import traceback
56 import traceback
57
57
58 from browser import document as doc
58 from browser import document as doc
59 from browser import window, alert, console
59 from browser import window, alert, console
60
60
61 _credits = """ Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
61 _credits = """ Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
62 for supporting Python development. See www.python.org for more information."""
62 for supporting Python development. See www.python.org for more information."""
63
63
64 _copyright = """Copyright (c) 2012, Pierre Quentel pierre.quentel@gmail.com
64 _copyright = """Copyright (c) 2012, Pierre Quentel pierre.quentel@gmail.com
65 All Rights Reserved.
65 All Rights Reserved.
66
66
67 Copyright (c) 2001-2013 Python Software Foundation.
67 Copyright (c) 2001-2013 Python Software Foundation.
68 All Rights Reserved.
68 All Rights Reserved.
69
69
70 Copyright (c) 2000 BeOpen.com.
70 Copyright (c) 2000 BeOpen.com.
71 All Rights Reserved.
71 All Rights Reserved.
72
72
73 Copyright (c) 1995-2001 Corporation for National Research Initiatives.
73 Copyright (c) 1995-2001 Corporation for National Research Initiatives.
74 All Rights Reserved.
74 All Rights Reserved.
75
75
76 Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
76 Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
77 All Rights Reserved."""
77 All Rights Reserved."""
78
78
79 _license = """Copyright (c) 2012, Pierre Quentel pierre.quentel@gmail.com
79 _license = """Copyright (c) 2012, Pierre Quentel pierre.quentel@gmail.com
80 All rights reserved.
80 All rights reserved.
81
81
82 Redistribution and use in source and binary forms, with or without
82 Redistribution and use in source and binary forms, with or without
83 modification, are permitted provided that the following conditions are met:
83 modification, are permitted provided that the following conditions are met:
84
84
85 Redistributions of source code must retain the above copyright notice, this
85 Redistributions of source code must retain the above copyright notice, this
86 list of conditions and the following disclaimer. Redistributions in binary
86 list of conditions and the following disclaimer. Redistributions in binary
87 form must reproduce the above copyright notice, this list of conditions and
87 form must reproduce the above copyright notice, this list of conditions and
88 the following disclaimer in the documentation and/or other materials provided
88 the following disclaimer in the documentation and/or other materials provided
89 with the distribution.
89 with the distribution.
90 Neither the name of the <ORGANIZATION> nor the names of its contributors may
90 Neither the name of the <ORGANIZATION> nor the names of its contributors may
91 be used to endorse or promote products derived from this software without
91 be used to endorse or promote products derived from this software without
92 specific prior written permission.
92 specific prior written permission.
93
93
94 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
94 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
95 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
95 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
96 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
96 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
97 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
97 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
98 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
98 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
99 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
99 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
100 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
100 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
101 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
101 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
102 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
102 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
103 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
103 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
104 POSSIBILITY OF SUCH DAMAGE.
104 POSSIBILITY OF SUCH DAMAGE.
105 """
105 """
106
106
107 def credits():
107 def credits():
108 print(_credits)
108 print(_credits)
109 credits.__repr__ = lambda:_credits
109 credits.__repr__ = lambda:_credits
110
110
111 def copyright():
111 def copyright():
112 print(_copyright)
112 print(_copyright)
113 copyright.__repr__ = lambda:_copyright
113 copyright.__repr__ = lambda:_copyright
114
114
115 def license():
115 def license():
116 print(_license)
116 print(_license)
117 license.__repr__ = lambda:_license
117 license.__repr__ = lambda:_license
118
118
119 def write(data):
119 def write(data):
120 doc['console'].value += str(data)
120 doc['console'].value += str(data)
121
121
122
122
123 sys.stdout.write = sys.stderr.write = write
123 sys.stdout.write = sys.stderr.write = write
124 history = []
124 history = []
125 current = 0
125 current = 0
126 _status = "main" # or "block" if typing inside a block
126 _status = "main" # or "block" if typing inside a block
127
127
128 # execution namespace
128 # execution namespace
129 editor_ns = {'credits':credits,
129 editor_ns = {'credits':credits,
130 'copyright':copyright,
130 'copyright':copyright,
131 'license':license,
131 'license':license,
132 '__name__':'__main__'}
132 '__name__':'__main__'}
133
133
134 def cursorToEnd(*args):
134 def cursorToEnd(*args):
135 pos = len(doc['console'].value)
135 pos = len(doc['console'].value)
136 doc['console'].setSelectionRange(pos, pos)
136 doc['console'].setSelectionRange(pos, pos)
137 doc['console'].scrollTop = doc['console'].scrollHeight
137 doc['console'].scrollTop = doc['console'].scrollHeight
138
138
139 def get_col(area):
139 def get_col(area):
140 # returns the column num of cursor
140 # returns the column num of cursor
141 sel = doc['console'].selectionStart
141 sel = doc['console'].selectionStart
142 lines = doc['console'].value.split('\n')
142 lines = doc['console'].value.split('\n')
143 for line in lines[:-1]:
143 for line in lines[:-1]:
144 sel -= len(line) + 1
144 sel -= len(line) + 1
145 return sel
145 return sel
146
146
147
147
148 def myKeyPress(event):
148 def myKeyPress(event):
149 global _status, current
149 global _status, current
150 if event.keyCode == 9: # tab key
150 if event.keyCode == 9: # tab key
151 event.preventDefault()
151 event.preventDefault()
152 doc['console'].value += " "
152 doc['console'].value += " "
153 elif event.keyCode == 13: # return
153 elif event.keyCode == 13: # return
154 src = doc['console'].value
154 src = doc['console'].value
155 if _status == "main":
155 if _status == "main":
156 currentLine = src[src.rfind('>>>') + 4:]
156 currentLine = src[src.rfind('>>>') + 4:]
157 elif _status == "3string":
157 elif _status == "3string":
158 currentLine = src[src.rfind('>>>') + 4:]
158 currentLine = src[src.rfind('>>>') + 4:]
159 currentLine = currentLine.replace('\n... ', '\n')
159 currentLine = currentLine.replace('\n... ', '\n')
160 else:
160 else:
161 currentLine = src[src.rfind('...') + 4:]
161 currentLine = src[src.rfind('...') + 4:]
162 if _status == 'main' and not currentLine.strip():
162 if _status == 'main' and not currentLine.strip():
163 doc['console'].value += '\n>>> '
163 doc['console'].value += '\n>>> '
164 event.preventDefault()
164 event.preventDefault()
165 return
165 return
166 doc['console'].value += '\n'
166 doc['console'].value += '\n'
167 history.append(currentLine)
167 history.append(currentLine)
168 current = len(history)
168 current = len(history)
169 if _status == "main" or _status == "3string":
169 if _status == "main" or _status == "3string":
170 try:
170 try:
171 _ = editor_ns['_'] = eval(currentLine, editor_ns)
171 _ = editor_ns['_'] = eval(currentLine, editor_ns)
172 if _ is not None:
172 if _ is not None:
173 write(repr(_)+'\n')
173 write(repr(_)+'\n')
174 doc['console'].value += '>>> '
174 doc['console'].value += '>>> '
175 _status = "main"
175 _status = "main"
176 except IndentationError:
176 except IndentationError:
177 doc['console'].value += '... '
177 doc['console'].value += '... '
178 _status = "block"
178 _status = "block"
179 except SyntaxError as msg:
179 except SyntaxError as msg:
180 if str(msg) == 'invalid syntax : triple string end not found' or \
180 if str(msg) == 'invalid syntax : triple string end not found' or \
181 str(msg).startswith('Unbalanced bracket'):
181 str(msg).startswith('Unbalanced bracket'):
182 doc['console'].value += '... '
182 doc['console'].value += '... '
183 _status = "3string"
183 _status = "3string"
184 elif str(msg) == 'eval() argument must be an expression':
184 elif str(msg) == 'eval() argument must be an expression':
185 try:
185 try:
186 exec(currentLine, editor_ns)
186 exec(currentLine, editor_ns)
187 except:
187 except:
188 traceback.print_exc()
188 traceback.print_exc()
189 doc['console'].value += '>>> '
189 doc['console'].value += '>>> '
190 _status = "main"
190 _status = "main"
191 elif str(msg) == 'decorator expects function':
191 elif str(msg) == 'decorator expects function':
192 doc['console'].value += '... '
192 doc['console'].value += '... '
193 _status = "block"
193 _status = "block"
194 else:
194 else:
195 traceback.print_exc()
195 traceback.print_exc()
196 doc['console'].value += '>>> '
196 doc['console'].value += '>>> '
197 _status = "main"
197 _status = "main"
198 except:
198 except:
199 traceback.print_exc()
199 traceback.print_exc()
200 doc['console'].value += '>>> '
200 doc['console'].value += '>>> '
201 _status = "main"
201 _status = "main"
202 elif currentLine == "": # end of block
202 elif currentLine == "": # end of block
203 block = src[src.rfind('>>>') + 4:].splitlines()
203 block = src[src.rfind('>>>') + 4:].splitlines()
204 block = [block[0]] + [b[4:] for b in block[1:]]
204 block = [block[0]] + [b[4:] for b in block[1:]]
205 block_src = '\n'.join(block)
205 block_src = '\n'.join(block)
206 # status must be set before executing code in globals()
206 # status must be set before executing code in globals()
207 _status = "main"
207 _status = "main"
208 try:
208 try:
209 _ = exec(block_src, editor_ns)
209 _ = exec(block_src, editor_ns)
210 if _ is not None:
210 if _ is not None:
211 print(repr(_))
211 print(repr(_))
212 except:
212 except:
213 traceback.print_exc()
213 traceback.print_exc()
214 doc['console'].value += '>>> '
214 doc['console'].value += '>>> '
215 else:
215 else:
216 doc['console'].value += '... '
216 doc['console'].value += '... '
217
217
218 cursorToEnd()
218 cursorToEnd()
219 event.preventDefault()
219 event.preventDefault()
220
220
221 def myKeyDown(event):
221 def myKeyDown(event):
222 global _status, current
222 global _status, current
223 if event.keyCode == 37: # left arrow
223 if event.keyCode == 37: # left arrow
224 sel = get_col(doc['console'])
224 sel = get_col(doc['console'])
225 if sel < 5:
225 if sel < 5:
226 event.preventDefault()
226 event.preventDefault()
227 event.stopPropagation()
227 event.stopPropagation()
228 elif event.keyCode == 36: # line start
228 elif event.keyCode == 36: # line start
229 pos = doc['console'].selectionStart
229 pos = doc['console'].selectionStart
230 col = get_col(doc['console'])
230 col = get_col(doc['console'])
231 doc['console'].setSelectionRange(pos - col + 4, pos - col + 4)
231 doc['console'].setSelectionRange(pos - col + 4, pos - col + 4)
232 event.preventDefault()
232 event.preventDefault()
233 elif event.keyCode == 38: # up
233 elif event.keyCode == 38: # up
234 if current > 0:
234 if current > 0:
235 pos = doc['console'].selectionStart
235 pos = doc['console'].selectionStart
236 col = get_col(doc['console'])
236 col = get_col(doc['console'])
237 # remove current line
237 # remove current line
238 doc['console'].value = doc['console'].value[:pos - col + 4]
238 doc['console'].value = doc['console'].value[:pos - col + 4]
239 current -= 1
239 current -= 1
240 doc['console'].value += history[current]
240 doc['console'].value += history[current]
241 event.preventDefault()
241 event.preventDefault()
242 elif event.keyCode == 40: # down
242 elif event.keyCode == 40: # down
243 if current < len(history) - 1:
243 if current < len(history) - 1:
244 pos = doc['console'].selectionStart
244 pos = doc['console'].selectionStart
245 col = get_col(doc['console'])
245 col = get_col(doc['console'])
246 # remove current line
246 # remove current line
247 doc['console'].value = doc['console'].value[:pos - col + 4]
247 doc['console'].value = doc['console'].value[:pos - col + 4]
248 current += 1
248 current += 1
249 doc['console'].value += history[current]
249 doc['console'].value += history[current]
250 event.preventDefault()
250 event.preventDefault()
251 elif event.keyCode == 8: # backspace
251 elif event.keyCode == 8: # backspace
252 src = doc['console'].value
252 src = doc['console'].value
253 lstart = src.rfind('\n')
253 lstart = src.rfind('\n')
254 if (lstart == -1 and len(src) < 5) or (len(src) - lstart < 6):
254 if (lstart == -1 and len(src) < 5) or (len(src) - lstart < 6):
255 event.preventDefault()
255 event.preventDefault()
256 event.stopPropagation()
256 event.stopPropagation()
257
257
258
258
259 doc['console'].bind('keypress', myKeyPress)
259 doc['console'].bind('keypress', myKeyPress)
260 doc['console'].bind('keydown', myKeyDown)
260 doc['console'].bind('keydown', myKeyDown)
261 doc['console'].bind('click', cursorToEnd)
261 doc['console'].bind('click', cursorToEnd)
262 v = sys.implementation.version
262 v = sys.implementation.version
263 doc['console'].value = "Brython %s.%s.%s on %s %s\n>>> " % (
263 doc['console'].value = "Brython %s.%s.%s on %s %s\n>>> " % (
264 v[0], v[1], v[2], window.navigator.appName, window.navigator.appVersion)
264 v[0], v[1], v[2], window.navigator.appName, window.navigator.appVersion)
265 #doc['console'].value += 'Type "copyright", "credits" or "license" for more information.'
265 #doc['console'].value += 'Type "copyright", "credits" or "license" for more information.'
266 doc['console'].focus()
266 doc['console'].focus()
267 cursorToEnd()
267 cursorToEnd()
268
268
269
269
270
270
271
271
@@ -1,72 +1,72
1 require File.expand_path('../boot', __FILE__)
1 require File.expand_path('../boot', __FILE__)
2
2
3 require 'rails/all'
3 require 'rails/all'
4
4
5 if defined?(Bundler)
5 if defined?(Bundler)
6 # If you precompile assets before deploying to production, use this line
6 # If you precompile assets before deploying to production, use this line
7 Bundler.require(*Rails.groups(:assets => %w(development test)))
7 Bundler.require(*Rails.groups(:assets => %w(development test)))
8 # If you want your assets lazily compiled in production, use this line
8 # If you want your assets lazily compiled in production, use this line
9 # Bundler.require(:default, :assets, Rails.env)
9 # Bundler.require(:default, :assets, Rails.env)
10 end
10 end
11
11
12 module CafeGrader
12 module CafeGrader
13 class Application < Rails::Application
13 class Application < Rails::Application
14 # Settings in config/environments/* take precedence over those specified here.
14 # Settings in config/environments/* take precedence over those specified here.
15 # Application configuration should go into files in config/initializers
15 # Application configuration should go into files in config/initializers
16 # -- all .rb files in that directory are automatically loaded.
16 # -- all .rb files in that directory are automatically loaded.
17
17
18 # Custom directories with classes and modules you want to be autoloadable.
18 # Custom directories with classes and modules you want to be autoloadable.
19 config.autoload_paths += %W(#{config.root}/lib)
19 config.autoload_paths += %W(#{config.root}/lib)
20
20
21 # Only load the plugins named here, in the order given (default is alphabetical).
21 # Only load the plugins named here, in the order given (default is alphabetical).
22 # :all can be used as a placeholder for all plugins not explicitly named.
22 # :all can be used as a placeholder for all plugins not explicitly named.
23 # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
23 # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
24
24
25 # Activate observers that should always be running.
25 # Activate observers that should always be running.
26 # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
26 # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
27
27
28 # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
28 # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
29 # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
29 # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
30 config.time_zone = 'UTC'
30 config.time_zone = 'UTC'
31
31
32 # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
32 # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
33 # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
33 # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
34 config.i18n.default_locale = :en
34 config.i18n.default_locale = :en
35
35
36 # Configure the default encoding used in templates for Ruby 1.9.
36 # Configure the default encoding used in templates for Ruby 1.9.
37 config.encoding = "utf-8"
37 config.encoding = "utf-8"
38
38
39 # Configure sensitive parameters which will be filtered from the log file.
39 # Configure sensitive parameters which will be filtered from the log file.
40 config.filter_parameters += [:password]
40 config.filter_parameters += [:password]
41
41
42 # Enable escaping HTML in JSON.
42 # Enable escaping HTML in JSON.
43 config.active_support.escape_html_entities_in_json = true
43 config.active_support.escape_html_entities_in_json = true
44
44
45 # Use SQL instead of Active Record's schema dumper when creating the database.
45 # Use SQL instead of Active Record's schema dumper when creating the database.
46 # This is necessary if your schema can't be completely dumped by the schema dumper,
46 # This is necessary if your schema can't be completely dumped by the schema dumper,
47 # like if you have constraints or database-specific column types
47 # like if you have constraints or database-specific column types
48 # config.active_record.schema_format = :sql
48 # config.active_record.schema_format = :sql
49
49
50 # Enable the asset pipeline
50 # Enable the asset pipeline
51 config.assets.enabled = true
51 config.assets.enabled = true
52
52
53 # Version of your assets, change this if you want to expire all your assets
53 # Version of your assets, change this if you want to expire all your assets
54 config.assets.version = '1.0'
54 config.assets.version = '1.0'
55
55
56 # ---------------- IMPORTANT ----------------------
56 # ---------------- IMPORTANT ----------------------
57 # If we deploy the app into a subdir name "grader", be sure to do "rake assets:precompile RAILS_RELATIVE_URL_ROOT=/grader"
57 # If we deploy the app into a subdir name "grader", be sure to do "rake assets:precompile RAILS_RELATIVE_URL_ROOT=/grader"
58 # moreover, using the following line instead also known to works
58 # moreover, using the following line instead also known to works
59 #config.action_controller.relative_url_root = '/grader'
59 #config.action_controller.relative_url_root = '/grader'
60
60
61 #font path
61 #font path
62 config.assets.paths << "#{Rails}/vendor/assets/fonts"
62 config.assets.paths << "#{Rails}/vendor/assets/fonts"
63
63
64 config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
64 config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
65 config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
65 config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
66 %w( announcements submissions configurations contests contest_management graders heartbeat
66 %w( announcements submissions configurations contests contest_management graders heartbeat
67 login main messages problems report site sites sources tasks
67 login main messages problems report site sites sources tasks
68 - test user_admin users ).each do |controller|
68 + test user_admin users testcases).each do |controller|
69 config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
69 config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
70 end
70 end
71 end
71 end
72 end
72 end
@@ -1,23 +1,23
1 # Be sure to restart your server when you modify this file.
1 # Be sure to restart your server when you modify this file.
2
2
3 # Version of your assets, change this if you want to expire all your assets.
3 # Version of your assets, change this if you want to expire all your assets.
4 Rails.application.config.assets.version = '1.0'
4 Rails.application.config.assets.version = '1.0'
5
5
6 # Add additional assets to the asset load path.
6 # Add additional assets to the asset load path.
7 # Rails.application.config.assets.paths << Emoji.images_path
7 # Rails.application.config.assets.paths << Emoji.images_path
8 # Add Yarn node_modules folder to the asset load path.
8 # Add Yarn node_modules folder to the asset load path.
9 Rails.application.config.assets.paths << Rails.root.join('node_modules')
9 Rails.application.config.assets.paths << Rails.root.join('node_modules')
10 Rails.application.config.assets.paths << Rails.root.join('vendor/assets/fonts')
10 Rails.application.config.assets.paths << Rails.root.join('vendor/assets/fonts')
11
11
12 # Precompile additional assets.
12 # Precompile additional assets.
13 # application.js, application.css, and all non-JS/CSS in the app/assets
13 # application.js, application.css, and all non-JS/CSS in the app/assets
14 # folder are already added.
14 # folder are already added.
15 # Rails.application.config.assets.precompile += %w( admin.js admin.css )
15 # Rails.application.config.assets.precompile += %w( admin.js admin.css )
16
16
17 Rails.application.config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
17 Rails.application.config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
18 Rails.application.config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
18 Rails.application.config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
19 %w( announcements submissions configurations contests contest_management graders heartbeat
19 %w( announcements submissions configurations contests contest_management graders heartbeat
20 login main messages problems report site sites sources tasks groups
20 login main messages problems report site sites sources tasks groups
21 - test user_admin users tags).each do |controller|
21 + test user_admin users tags testcases).each do |controller|
22 Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
22 Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
23 end
23 end
@@ -1,73 +1,74
1 module GraderScript
1 module GraderScript
2
2
3 def self.grader_control_enabled?
3 def self.grader_control_enabled?
4 if defined? GRADER_ROOT_DIR
4 if defined? GRADER_ROOT_DIR
5 GRADER_ROOT_DIR != ''
5 GRADER_ROOT_DIR != ''
6 else
6 else
7 false
7 false
8 end
8 end
9 end
9 end
10
10
11 def self.raw_dir
11 def self.raw_dir
12 File.join GRADER_ROOT_DIR, "raw"
12 File.join GRADER_ROOT_DIR, "raw"
13 end
13 end
14
14
15 def self.call_grader(params)
15 def self.call_grader(params)
16 if GraderScript.grader_control_enabled?
16 if GraderScript.grader_control_enabled?
17 cmd = File.join(GRADER_ROOT_DIR, "scripts/grader") + " " + params
17 cmd = File.join(GRADER_ROOT_DIR, "scripts/grader") + " " + params
18 system(cmd)
18 system(cmd)
19 end
19 end
20 end
20 end
21
21
22 def self.stop_grader(pid)
22 def self.stop_grader(pid)
23 GraderScript.call_grader "stop #{pid}"
23 GraderScript.call_grader "stop #{pid}"
24 end
24 end
25
25
26 def self.stop_graders(pids)
26 def self.stop_graders(pids)
27 pid_str = (pids.map { |process| process.pid.to_s }).join ' '
27 pid_str = (pids.map { |process| process.pid.to_s }).join ' '
28 GraderScript.call_grader "stop #{pid_str}"
28 GraderScript.call_grader "stop #{pid_str}"
29 end
29 end
30
30
31 def self.start_grader(env)
31 def self.start_grader(env)
32 GraderScript.call_grader "#{env} queue --err-log &"
32 GraderScript.call_grader "#{env} queue --err-log &"
33 GraderScript.call_grader "#{env} test_request -err-log &"
33 GraderScript.call_grader "#{env} test_request -err-log &"
34 end
34 end
35
35
36 + #call the import problem script
36 def self.call_import_problem(problem_name,
37 def self.call_import_problem(problem_name,
37 problem_dir,
38 problem_dir,
38 time_limit=1,
39 time_limit=1,
39 memory_limit=32,
40 memory_limit=32,
40 checker_name='text')
41 checker_name='text')
41 if GraderScript.grader_control_enabled?
42 if GraderScript.grader_control_enabled?
42 cur_dir = `pwd`.chomp
43 cur_dir = `pwd`.chomp
43 Dir.chdir(GRADER_ROOT_DIR)
44 Dir.chdir(GRADER_ROOT_DIR)
44
45
45 script_name = File.join(GRADER_ROOT_DIR, "scripts/import_problem")
46 script_name = File.join(GRADER_ROOT_DIR, "scripts/import_problem")
46 cmd = "#{script_name} #{problem_name} #{problem_dir} #{checker_name}" +
47 cmd = "#{script_name} #{problem_name} #{problem_dir} #{checker_name}" +
47 " -t #{time_limit} -m #{memory_limit}"
48 " -t #{time_limit} -m #{memory_limit}"
48
49
49 output = `#{cmd}`
50 output = `#{cmd}`
50
51
51 Dir.chdir(cur_dir)
52 Dir.chdir(cur_dir)
52
53
53 return "import CMD: #{cmd}\n" + output
54 return "import CMD: #{cmd}\n" + output
54 end
55 end
55 return ''
56 return ''
56 end
57 end
57
58
58 def self.call_import_testcase(problem_name)
59 def self.call_import_testcase(problem_name)
59 if GraderScript.grader_control_enabled?
60 if GraderScript.grader_control_enabled?
60 cur_dir = `pwd`.chomp
61 cur_dir = `pwd`.chomp
61 Dir.chdir(GRADER_ROOT_DIR)
62 Dir.chdir(GRADER_ROOT_DIR)
62
63
63 script_name = File.join(GRADER_ROOT_DIR, "scripts/load_testcase")
64 script_name = File.join(GRADER_ROOT_DIR, "scripts/load_testcase")
64 cmd = "#{script_name} #{problem_name}"
65 cmd = "#{script_name} #{problem_name}"
65
66
66 output = `#{cmd}`
67 output = `#{cmd}`
67
68
68 Dir.chdir(cur_dir)
69 Dir.chdir(cur_dir)
69 return "Testcase import result:\n" + output
70 return "Testcase import result:\n" + output
70 end
71 end
71 end
72 end
72
73
73 end
74 end
@@ -1,193 +1,195
1 require 'tmpdir'
1 require 'tmpdir'
2
2
3 class TestdataImporter
3 class TestdataImporter
4
4
5 attr :log_msg
5 attr :log_msg
6
6
7 def initialize(problem)
7 def initialize(problem)
8 @problem = problem
8 @problem = problem
9 end
9 end
10
10
11 + #Create or update problem according to the parameter
11 def import_from_file(tempfile,
12 def import_from_file(tempfile,
12 time_limit,
13 time_limit,
13 memory_limit,
14 memory_limit,
14 checker_name='text',
15 checker_name='text',
15 import_to_db=false)
16 import_to_db=false)
16
17
17 dirname = extract(tempfile)
18 dirname = extract(tempfile)
18 return false if not dirname
19 return false if not dirname
19 if not import_to_db
20 if not import_to_db
20 @log_msg = GraderScript.call_import_problem(@problem.name,
21 @log_msg = GraderScript.call_import_problem(@problem.name,
21 dirname,
22 dirname,
22 time_limit,
23 time_limit,
23 memory_limit,
24 memory_limit,
24 checker_name)
25 checker_name)
25 else
26 else
26 # Import test data to test pairs.
27 # Import test data to test pairs.
27
28
28 @problem.test_pairs.clear
29 @problem.test_pairs.clear
29 if import_test_pairs(dirname)
30 if import_test_pairs(dirname)
30 test_pair_count = TestPair.count :conditions => "problem_id = #{@problem.id}"
31 test_pair_count = TestPair.count :conditions => "problem_id = #{@problem.id}"
31 @log_msg = "Importing test pair successful. (#{test_pair_count} test pairs imported)"
32 @log_msg = "Importing test pair successful. (#{test_pair_count} test pairs imported)"
32 else
33 else
33 @log_msg = "Importing test pair failed. (0 test pairs imported)"
34 @log_msg = "Importing test pair failed. (0 test pairs imported)"
34 end
35 end
35 end
36 end
36
37
37 @log_msg << import_problem_description(dirname)
38 @log_msg << import_problem_description(dirname)
38 @log_msg << import_problem_pdf(dirname)
39 @log_msg << import_problem_pdf(dirname)
39 @log_msg << import_full_score(dirname)
40 @log_msg << import_full_score(dirname)
40
41
41 #import test data
42 #import test data
42 @log_msg << GraderScript.call_import_testcase(@problem.name)
43 @log_msg << GraderScript.call_import_testcase(@problem.name)
43
44
44 return true
45 return true
45 end
46 end
46
47
47 protected
48 protected
48
49
49 def self.long_ext(filename)
50 def self.long_ext(filename)
50 i = filename.index('.')
51 i = filename.index('.')
51 len = filename.length
52 len = filename.length
52 return filename.slice(i..len)
53 return filename.slice(i..len)
53 end
54 end
54
55
56 + # extract an archive file located at +tempfile+ to the +raw_dir+
55 def extract(tempfile)
57 def extract(tempfile)
56 testdata_filename = save_testdata_file(tempfile)
58 testdata_filename = save_testdata_file(tempfile)
57 ext = TestdataImporter.long_ext(tempfile.original_filename)
59 ext = TestdataImporter.long_ext(tempfile.original_filename)
58
60
59 extract_dir = File.join(GraderScript.raw_dir, @problem.name)
61 extract_dir = File.join(GraderScript.raw_dir, @problem.name)
60 if File.exists? extract_dir
62 if File.exists? extract_dir
61 backup_count = 0
63 backup_count = 0
62 begin
64 begin
63 backup_count += 1
65 backup_count += 1
64 backup_dirname = "#{extract_dir}.backup.#{backup_count}"
66 backup_dirname = "#{extract_dir}.backup.#{backup_count}"
65 end while File.exists? backup_dirname
67 end while File.exists? backup_dirname
66 File.rename(extract_dir, backup_dirname)
68 File.rename(extract_dir, backup_dirname)
67 end
69 end
68 Dir.mkdir extract_dir
70 Dir.mkdir extract_dir
69
71
70 if ext=='.tar.gz' or ext=='.tgz'
72 if ext=='.tar.gz' or ext=='.tgz'
71 cmd = "tar -zxvf #{testdata_filename} -C #{extract_dir}"
73 cmd = "tar -zxvf #{testdata_filename} -C #{extract_dir}"
72 elsif ext=='.tar'
74 elsif ext=='.tar'
73 cmd = "tar -xvf #{testdata_filename} -C #{extract_dir}"
75 cmd = "tar -xvf #{testdata_filename} -C #{extract_dir}"
74 elsif ext=='.zip'
76 elsif ext=='.zip'
75 cmd = "unzip -o #{testdata_filename} -d #{extract_dir}"
77 cmd = "unzip -o #{testdata_filename} -d #{extract_dir}"
76 else
78 else
77 return nil
79 return nil
78 end
80 end
79
81
80 system(cmd)
82 system(cmd)
81
83
82 files = Dir["#{extract_dir}/**/*1*.in"]
84 files = Dir["#{extract_dir}/**/*1*.in"]
83 return nil if files.length==0
85 return nil if files.length==0
84
86
85 File.delete(testdata_filename)
87 File.delete(testdata_filename)
86
88
87 return File.dirname(files[0])
89 return File.dirname(files[0])
88 end
90 end
89
91
90 def save_testdata_file(tempfile)
92 def save_testdata_file(tempfile)
91 ext = TestdataImporter.long_ext(tempfile.original_filename)
93 ext = TestdataImporter.long_ext(tempfile.original_filename)
92 testdata_filename = File.join(Dir.tmpdir,"#{@problem.name}#{ext}")
94 testdata_filename = File.join(Dir.tmpdir,"#{@problem.name}#{ext}")
93
95
94 return nil if tempfile==""
96 return nil if tempfile==""
95
97
96 if tempfile.instance_of?(Tempfile)
98 if tempfile.instance_of?(Tempfile)
97 tempfile.close
99 tempfile.close
98 FileUtils.move(tempfile.path,testdata_filename)
100 FileUtils.move(tempfile.path,testdata_filename)
99 else
101 else
100 File.open(testdata_filename, "wb") do |f|
102 File.open(testdata_filename, "wb") do |f|
101 f.write(tempfile.read)
103 f.write(tempfile.read)
102 end
104 end
103 end
105 end
104
106
105 return testdata_filename
107 return testdata_filename
106 end
108 end
107
109
108 def import_test_pairs(dirname)
110 def import_test_pairs(dirname)
109 test_num = 1
111 test_num = 1
110 while FileTest.exists? "#{dirname}/#{test_num}.in"
112 while FileTest.exists? "#{dirname}/#{test_num}.in"
111 in_filename = "#{dirname}/#{test_num}.in"
113 in_filename = "#{dirname}/#{test_num}.in"
112 sol_filename = "#{dirname}/#{test_num}.sol"
114 sol_filename = "#{dirname}/#{test_num}.sol"
113
115
114 break if not FileTest.exists? sol_filename
116 break if not FileTest.exists? sol_filename
115
117
116 test_pair = TestPair.new(:input => open(in_filename).read,
118 test_pair = TestPair.new(:input => open(in_filename).read,
117 :solution => open(sol_filename).read,
119 :solution => open(sol_filename).read,
118 :problem => @problem)
120 :problem => @problem)
119 break if not test_pair.save
121 break if not test_pair.save
120
122
121 test_num += 1
123 test_num += 1
122 end
124 end
123 return test_num > 1
125 return test_num > 1
124 end
126 end
125
127
126 def import_problem_description(dirname)
128 def import_problem_description(dirname)
127 html_files = Dir["#{dirname}/*.html"]
129 html_files = Dir["#{dirname}/*.html"]
128 markdown_files = Dir["#{dirname}/*.md"] + Dir["#{dirname}/*.markdown"]
130 markdown_files = Dir["#{dirname}/*.md"] + Dir["#{dirname}/*.markdown"]
129 if (html_files.length != 0) or (markdown_files.length != 0)
131 if (html_files.length != 0) or (markdown_files.length != 0)
130 description = @problem.description || Description.new
132 description = @problem.description || Description.new
131
133
132 if html_files.length != 0
134 if html_files.length != 0
133 filename = html_files[0]
135 filename = html_files[0]
134 description.markdowned = false
136 description.markdowned = false
135 else
137 else
136 filename = markdown_files[0]
138 filename = markdown_files[0]
137 description.markdowned = true
139 description.markdowned = true
138 end
140 end
139
141
140 description.body = open(filename).read
142 description.body = open(filename).read
141 description.save
143 description.save
142 @problem.description = description
144 @problem.description = description
143 @problem.save
145 @problem.save
144 return "\nProblem description imported from #{filename}."
146 return "\nProblem description imported from #{filename}."
145 else
147 else
146 return ''
148 return ''
147 end
149 end
148 end
150 end
149
151
150 def import_problem_pdf(dirname)
152 def import_problem_pdf(dirname)
151 pdf_files = Dir["#{dirname}/*.pdf"]
153 pdf_files = Dir["#{dirname}/*.pdf"]
152 puts "CHECKING... #{dirname}"
154 puts "CHECKING... #{dirname}"
153 if pdf_files.length != 0
155 if pdf_files.length != 0
154 puts "HAS PDF FILE"
156 puts "HAS PDF FILE"
155 filename = pdf_files[0]
157 filename = pdf_files[0]
156
158
157 @problem.save if not @problem.id
159 @problem.save if not @problem.id
158 out_dirname = "#{Problem.download_file_basedir}/#{@problem.id}"
160 out_dirname = "#{Problem.download_file_basedir}/#{@problem.id}"
159 if not FileTest.exists? out_dirname
161 if not FileTest.exists? out_dirname
160 Dir.mkdir out_dirname
162 Dir.mkdir out_dirname
161 end
163 end
162
164
163 out_filename = "#{out_dirname}/#{@problem.name}.pdf"
165 out_filename = "#{out_dirname}/#{@problem.name}.pdf"
164
166
165 if FileTest.exists? out_filename
167 if FileTest.exists? out_filename
166 File.delete out_filename
168 File.delete out_filename
167 end
169 end
168
170
169 File.rename(filename, out_filename)
171 File.rename(filename, out_filename)
170 @problem.description_filename = "#{@problem.name}.pdf"
172 @problem.description_filename = "#{@problem.name}.pdf"
171 @problem.save
173 @problem.save
172 return "\nProblem pdf imported from #{filename}."
174 return "\nProblem pdf imported from #{filename}."
173 else
175 else
174 return ""
176 return ""
175 end
177 end
176 end
178 end
177
179
178 #just set the full score to the total number of test case
180 #just set the full score to the total number of test case
179 #it is not perfect but works on most normal use case
181 #it is not perfect but works on most normal use case
180 def import_full_score(dirname)
182 def import_full_score(dirname)
181 num = 0
183 num = 0
182 loop do
184 loop do
183 num += 1
185 num += 1
184 in_file = Dir["#{dirname}/#{num}*.in"]
186 in_file = Dir["#{dirname}/#{num}*.in"]
185 break if in_file.length == 0
187 break if in_file.length == 0
186 end
188 end
187 full_score = (num - 1) * 10
189 full_score = (num - 1) * 10
188 @problem.full_score = full_score
190 @problem.full_score = full_score
189 @problem.save
191 @problem.save
190 return "\nFull score is set to #{full_score}."
192 return "\nFull score is set to #{full_score}."
191 end
193 end
192
194
193 end
195 end
You need to be logged in to leave comments. Login now