-
Notifications
You must be signed in to change notification settings - Fork 236
Expand file tree
/
Copy pathhandin.rb
More file actions
executable file
·419 lines (364 loc) · 15.8 KB
/
handin.rb
File metadata and controls
executable file
·419 lines (364 loc) · 15.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
##
# Handles different handin methods, including web form, local_submit and log_submit
#
module AssessmentHandin
include AssessmentHandinCore
# handin - The generic default handin function.
# This function calls out to smaller helper functions which provide for
# specific functionality.
#
# validateHandin_forHTML() : Returns true or false if the handin is valid.
# saveHandin() : Does the actual process of saving the handin to the
# database and writing the handin file to Disk.
# sendJob_AddHTMLMessages(course, assessment, submissions): Autogrades the submission.
#
# validateHandin_forHTML() cannot modify the state of the world in any way. And it should
# call super() to enable any other functionality. The only reason to not call super()
# is if you want to prevent other functionality. You should be very careful about this.
#
# Any errors should be added to flash[:error] and return false or nil.
def handin
if @assessment.disable_handins?
flash[:error] = "Sorry, handins are disabled for this assessment."
redirect_to(action: :show)
return false
end
if @assessment.embedded_quiz
contents = params[:submission]["embedded_quiz_form_answer"].to_s
out_file = Tempfile.new('out.txt-')
out_file.puts(contents)
params[:submission]["file"] = out_file
elsif @assessment.github_submission_enabled && params["repo"].present? && params["branch"].present?
# get code from Github
github_integration = current_user.github_integration
begin
@tarfile_path = github_integration.clone_repo(params["repo"], params["branch"], @assessment.max_size * (2 ** 20))
rescue StandardError => msg
flash[:error] = msg
redirect_to(action: :show)
return
end
# Populate submission field for validation
params[:submission] = { "tar" => @tarfile_path }
git_tarfile_cleanup_path = @tarfile_path
redirect_to(action: :show) && return unless validateHandin_forGit
else
# validate the handin
redirect_to(action: :show) && return unless validateHandin_forHTML
end
# save the submissions
begin
submissions = saveHandin(params[:submission])
if git_tarfile_cleanup_path
system *%W(rm #{git_tarfile_cleanup_path})
end
rescue StandardError => exception
ExceptionNotifier.notify_exception(exception, env: request.env,
data: {
user: current_user,
course: @course,
assessment: @assessment,
})
COURSE_LOGGER.log("could not save handin: #{exception.class} (#{exception.message})")
submissions = nil
end
if @assessment.embedded_quiz
out_file.close
out_file.unlink
end
# make sure submission was correctly constructed and saved
unless submissions
# Avoid overwriting the flash[:error] set by saveHandin
if !flash[:error].nil? && !flash[:error].empty?
flash[:error] = "There was an error handing in your submission."
end
redirect_to(action: :show) && return
end
# autograde the submissions only if there are problems defined
if @assessment.problems.length == 0
flash[:error] = "There are no problems in this assessment."
elsif @assessment.has_autograder?
begin
sendJob_AddHTMLMessages(@course, @assessment, submissions)
rescue AssessmentAutogradeCore::AutogradeError => e
# error message already filled in by sendJob_AddHTMLMessages, we just
# log the error message
COURSE_LOGGER.log("SendJob failed for #{submissions[0].id}\n
User error message: #{flash[:error]}\n
error name: #{e.error_code}\n
additional error data: #{e.additional_data}")
end
end
redirect_to([:history, @course, @assessment]) && return
end
# method called when student makes
# unofficial submission in the database
def local_submit
@user = User.find_by(email: params[:user])
@cud = @user ? @course.course_user_data.find_by(user_id: @user.id) : nil
unless @cud
err = "ERROR: invalid username (#{params[:user]}) for class #{@course.id}"
render(plain: err, status: :bad_request) && return
end
@assessment = @course.assessments.find_by(name: params[:name])
if !@assessment
err = "ERROR: Invalid Assessment (#{params[:id]}) for course #{@course.id}"
render(plain: err, status: :bad_request) && return
elsif @assessment.remote_handin_path.nil?
err = "ERROR: Remote handins have not been enabled by the instructor."
render(plain: err, status: :bad_request) && return
end
personal_directory = @user.email + "_remote_handin_" + @assessment.name
remote_handin_dir = File.join(@assessment.remote_handin_path, personal_directory)
if params[:submit]
# They've copied their handin over, lets go grab it.
begin
handin_file = params[:submit]
if @assessment.max_submissions != -1
submission_count = @cud.submissions.where(assessment: @assessment).size
if submission_count >= @assessment.max_submissions
render(plain: "You have no remaining submissions for this assessment",
status: :bad_request) && return
end
end
render(plain: flash[:error], status: :bad_request) && return unless validateHandinForGroups_forHTML
# save the submissions
begin
submissions = saveHandin("local_submit_file" => File.join(remote_handin_dir, handin_file))
rescue StandardError => e
ExceptionNotifier.notify_exception(e, env: request.env,
data: {
user: current_user,
course: @course,
assessment: @assessment,
})
COURSE_LOGGER.log("Error Saving Submission:\n#{e}")
submissions = nil
end
# make sure submission was correctly constructed and saved
unless submissions
# Avoid overwriting the flash[:error] set by saveHandin
if !flash[:error].nil? && !flash[:error].empty?
flash[:error] = "There was an error handing in your submission."
end
render(plain: flash[:error], status: :bad_request) && return
end
# autograde the submissions
sendJob_AddHTMLMessages(@course, @assessment, submissions) if @assessment.has_autograder?
rescue StandardError => e
ExceptionNotifier.notify_exception(e, env: request.env,
data: {
user: current_user,
course: @course,
assessment: @assessment,
submission: submissions[0],
})
COURSE_LOGGER.log(e.to_s)
end
if submissions
COURSE_LOGGER.log("Submission received, ID##{submissions[0].id}")
else
err = "There was an error saving your submission. Please contact your course staff\n"
render(plain: err, status: :bad_request) && return
end
if @assessment.max_submissions != -1
remaining = @assessment.max_submissions - submissions.count
render(plain: " - You have #{remaining} submissions left\n") && return
end
render(plain: "Successfully submitted\n") && return
else
# Create a handin directory for them.
# The handin Directory really should not exist, as this script deletes it
# when it's done. However, if it's there, we'll try to remove an empty
# folder, else fail w/ error message.
if Dir.exist?(remote_handin_dir)
begin
FileUtils.rm_rf(remote_handin_dir)
rescue SystemCallError => exception
ExceptionNotifier.notify_exception(exception, env: request.env,
data: {
user: current_user,
course: @course,
assessment: @assessment,
})
render(plain: "WARNING: could not clear previous handin directory, please") && return
end
end
begin
Dir.mkdir(remote_handin_dir)
rescue SystemCallError
ExceptionNotifier.notify_exception(exception, env: request.env,
data: {
user: current_user,
course: @course,
assessment: @assessment,
})
COURSE_LOGGER.log("ERROR: Could not create handin directory. Please contact
#{Rails.configuration.school["support_email"]} with this error")
end
system("fs sa #{remote_handin_dir} #{@user.email} rlidw")
end
render(plain: remote_handin_dir) && return
end
# method called when student makes
# log submission in the database
def log_submit
@user = User.find_by(email: params[:user])
@cud = @user ? @course.course_user_data.find_by(user_id: @user.id) : nil
unless @cud
err = "ERROR: invalid username (#{params[:user]}) for class #{@course.id}"
render(plain: err, status: :bad_request) && return
end
@assessment = @course.assessments.find_by(name: params[:name])
if !@assessment
err = "ERROR: Invalid Assessment (#{params[:id]}) for course #{@course.id}"
render(plain: err, status: :bad_request) && return
elsif !@assessment.allow_unofficial
err = "ERROR: This assessment does not allow Log Submissions"
render(plain: err, status: :bad_request) && return
end
@result = params[:result]
render(plain: "ERROR: No result!", status: :bad_request) && return unless @result
# Everything looks OK, so append the autoresult to the log.txt file for this lab
ASSESSMENT_LOGGER.setAssessment(@assessment)
ASSESSMENT_LOGGER.log("#{@user.email},0,#{@result}")
# Load up the lab.rb file
mod_name = @assessment.name + (@course.name).gsub(/[^A-Za-z0-9]/, "")
require(Rails.root.join("assessmentConfig", "#{@course.name}-#{@assessment.name}.rb"))
eval("extend #{mod_name.camelcase}")
begin
# Call the parseAutoresult function defined in the lab.rb file. If
# the list of scores it returns is empty, then we the lab developer is
# asking us not to create an unofficial submission in the
# database. Simply return a successful status string to the client and
# exit.
scores = parseAutoresult(@result, false)
render(plain: "OK", status: 200) && return if scores.keys.length == 0
# Try to find an existing submission (always version 0).
submission = @assessment.submissions.find_by(version: 0, course_user_datum_id: @cud.id)
if !submission
submission = @assessment.submissions.new(
version: 0,
autoresult: @result,
user_id: @cud.id,
submitted_by_id: 0,
)
submission.save!
else
# update this one
submission.autoresult = @result
submission.created_at = Time.now
submission.save!
end
# Update the scores in the db's unofficial submission using the list
# returned by the parseAutoresult function
scores.keys.each do |key|
problem = @assessment.problems.find_by(name: key)
score = submission.scores.find_or_initialize_by(problem_id: problem.id)
score.score = scores[key]
score.released = true
score.grader_id = 0
score.save!
end
rescue StandardError => e
ExceptionNotifier.notify_exception(e, env: request.env,
data: {
user: current_user,
course: @course,
assessment: @assessment,
submission: submission,
})
COURSE_LOGGER.log(e.to_s)
end
render(plain: "OK", status: 200) && return
end
private
##
# this function checks that now is a valid time to submit and that the
# submission file is okay to submit.
#
def validateHandin_forHTML
if params[:submission].blank?
flash[:error] = "Submission was blank - please upload again."
return false
end
if params[:submission]["file"].blank? and params["repo"].blank?
flash[:error] = "Submission was blank (file upload/Github repository missing) - please try again."
return false
end
validate_custom_form
validity = validateHandin(params[:submission]["file"].size,
params[:submission]["file"].content_type,
params[:submission]["file"].original_filename)
return handle_validity(validity)
end
##
# Validates Git tarfile
#
def validateHandin_forGit
if @tarfile_path.blank?
flash[:error] = "Git submission error"
return false
end
validity = validateHandin(File.size(@tarfile_path),
MimeMagic.by_magic(File.open(@tarfile_path)).type,
@tarfile_path) # TODO probably want filename instead of path
return handle_validity(validity)
end
##
# this function makes sure that the submitter's group can submit.
# If the assessment does not have groups, or the user has no group,
# this returns true. Otherwise, it checks that everyone is confirmed
# to be in the group and that no one is over the submission limit.
#
def validateHandinForGroups_forHTML
validity = validateHandinForGroups
case validity
when :valid
return true
when :awaiting_member_confirmation
msg = "You cannot submit until all group members confirm their group membership"
when :group_submission_limit_exceeded
msg = "A member of your group has reached the submission limit for this assessment"
end
flash[:error] = msg
return false
end
def validate_custom_form
# check if custom form exists
if @assessment.has_custom_form
for i in 0..@assessment.getTextfields.size - 1
if params[:submission][("formfield" + (i + 1).to_s).to_sym].blank?
flash[:error] = @assessment.getTextfields[i] + " is a required field."
return false
end
end
end
end
def handle_validity(validity)
case validity
when :valid
return validateHandinForGroups_forHTML
when :handin_disabled
msg = "Sorry, handins are disabled for this assessment."
when :submission_empty
msg = "Submission was blank - please upload again."
when :file_too_large
msg = "Your submission is larger than the max allowed " \
"size (#{@assessment.max_size} MB) - please remove any " \
"unnecessary logfiles and binaries."
when :fail_type_check
flash[:error] = "" if flash[:error].nil?
msg = "Submission failed Filetype Check. " + flash[:error]
end
flash[:error] = msg
return false
end
def set_handin
submission_count = @assessment.submissions.where(course_user_datum_id: @cud.id).count
@left_count = [@assessment.max_submissions - submission_count, 0].max
@aud = AssessmentUserDatum.get @assessment.id, @cud.id
@can_submit, @why_not = @aud.can_submit? Time.now
@submission = Submission.new
end
end