File size: 8,197 Bytes
def1299
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
class SubmissionsController < ApplicationController
  before_action :authorize_request, only: [:index, :destroy]
  before_action :check_maintenance, only: [:create, :destroy]
  before_action :check_wait, only: [:create] # Wait in batch_create is not allowed
  before_action :check_batched_submissions, only: [:batch_create, :batch_show]
  before_action :check_queue_size, only: [:create, :batch_create]
  before_action :check_requested_fields, except: [:batch_create] # Fields are ignored in batch_create
  before_action :set_base64_encoded

  def index
    page = params[:page].try(:to_i) || 1
    per_page = params[:per_page].try(:to_i) || Submission.per_page

    if page <= 0
      render json: { error: "invalid page: #{page}" }, status: :bad_request
      return
    elsif per_page < 0
      render json: { error: "invalid per_page: #{per_page}" }, status: :bad_request
      return
    end

    submissions = Submission.paginate(page: page, per_page: per_page)
    serializable_submissions = ActiveModelSerializers::SerializableResource.new(
      submissions, { each_serializer: SubmissionSerializer, base64_encoded: @base64_encoded, fields: @requested_fields }
    )

    render json: {
      submissions: serializable_submissions.as_json,
      meta: pagination_dict(submissions)
    }
  rescue Encoding::UndefinedConversionError => e
    render json: {
      error: "some attributes for one or more submissions cannot be converted to UTF-8, use base64_encoded=true query parameter"
    }, status: :bad_request
  end

  def destroy
    if !Config::ENABLE_SUBMISSION_DELETE
      render json: { error: "delete not allowed" }, status: :bad_request
      return
    end

    submission = Submission.find_by!(token: params[:token])
    if submission.status == Status.queue || submission.status == Status.process
      render json: {
        error: "submission cannot be deleted because its status is #{submission.status.id} (#{submission.status.name})"
      }, status: :bad_request
      return
    end

    submission.delete

    # Forcing base64_encoded=true because it guarantees user will get requested data after delete.
    render json: submission, base64_encoded: true, fields: @requested_fields
  end

  def show
    token = params[:token]
    render json: Rails.cache.fetch("#{token}", expires_in: Config::SUBMISSION_CACHE_DURATION, race_condition_ttl: 0.1*Config::SUBMISSION_CACHE_DURATION) {
      Submission.find_by!(token: token)
    }, base64_encoded: @base64_encoded, fields: @requested_fields
  rescue Encoding::UndefinedConversionError
    render_conversion_error(:bad_request)
  end

  def batch_show
    tokens = (request.headers[:tokens] || params[:tokens]).to_s.strip.split(",")

    if tokens.length > Config::MAX_SUBMISSION_BATCH_SIZE
      render json: {
        error: "number of submissions in a batch should be less than or equal to #{Config::MAX_SUBMISSION_BATCH_SIZE}"
      }, status: :bad_request
      return
    elsif tokens.length == 0
      render json: {
        error: "there should be at least one submission in a batch"
      }, status: :bad_request
      return
    end

    existing_submissions = Hash[Submission.where(token: tokens).collect{ |s| [s.token, s] }]

    submissions = []
    tokens.each do |token|
      if existing_submissions.has_key?(token)
        serialized_submission  = ActiveModelSerializers::SerializableResource.new(
          existing_submissions[token], { serializer: SubmissionSerializer, base64_encoded: @base64_encoded, fields: @requested_fields }
        )
        submissions << serialized_submission.as_json
      else
        submissions << nil
      end
    end

    render json: { submissions: submissions }
  rescue Encoding::UndefinedConversionError => e
    render json: {
      error: "some attributes for one or more submissions cannot be converted to UTF-8, use base64_encoded=true query parameter"
    }, status: :bad_request
  end

  def create
    submission = Submission.new(submission_params(params))

    if submission.save
      if @wait
        begin
          IsolateRunner.perform_now(submission)
          submission.reload
          render json: submission, status: :created, base64_encoded: @base64_encoded, fields: @requested_fields
        rescue Encoding::UndefinedConversionError => e
          render_conversion_error(:created, submission.token)
        end
      else
        IsolateRunner.perform_later(submission)
        render json: submission, status: :created, fields: [:token]
      end
    else
      render json: submission.errors, status: :unprocessable_entity
    end
  end

  # Batch Create does not support sync (wait=true) mode.
  def batch_create
    number_of_submissions = params[:submissions].try(:size).to_i

    if number_of_submissions > Config::MAX_SUBMISSION_BATCH_SIZE
      render json: {
        error: "number of submissions in a batch should be less than or equal to #{Config::MAX_SUBMISSION_BATCH_SIZE}"
      }, status: :bad_request
      return
    elsif number_of_submissions == 0
      render json: {
        error: "there should be at least one submission in a batch"
      }, status: :bad_request
      return
    end

    submissions = params[:submissions].each.collect{ |p| Submission.new(submission_params(p)) }

    response = []
    has_valid_submission = false

    submissions.each do |submission|
      if submission.save
        IsolateRunner.perform_later(submission)
        response << { token: submission.token }
        has_valid_submission = true
      else
        response << submission.errors
      end
    end

    render json: response, status: has_valid_submission ? :created : :unprocessable_entity
  end

  private

  def submission_params(params)
    submission_params = params.permit(
      :source_code,
      :language_id,
      :compiler_options,
      :command_line_arguments,
      :number_of_runs,
      :stdin,
      :expected_output,
      :cpu_time_limit,
      :cpu_extra_time,
      :wall_time_limit,
      :memory_limit,
      :stack_limit,
      :max_processes_and_or_threads,
      :enable_per_process_and_thread_time_limit,
      :enable_per_process_and_thread_memory_limit,
      :max_file_size,
      :redirect_stderr_to_stdout,
      :callback_url,
      :additional_files,
      :enable_network
    )

    submission_params[:additional_files] = Base64Service.decode(submission_params[:additional_files])

    if @base64_encoded
      submission_params[:source_code] = Base64Service.decode(submission_params[:source_code])
      submission_params[:stdin] = Base64Service.decode(submission_params[:stdin])
      submission_params[:expected_output] = Base64Service.decode(submission_params[:expected_output])
    end

    submission_params
  end

  def check_wait
    @wait = params[:wait] == "true"
    if @wait && !Config::ENABLE_WAIT_RESULT
      render json: { error: "wait not allowed" }, status: :bad_request
    end
  end

  def check_batched_submissions
    unless Config::ENABLE_BATCHED_SUBMISSIONS
      render json: { error: "batched submissions are not allowed" }, status: :bad_request
    end
  end

  def check_queue_size
    number_of_submissions = params[:submissions].try(:size).presence || 1
    if Resque.size(ENV["JUDGE0_VERSION"]) + number_of_submissions > Config::MAX_QUEUE_SIZE
      render json: { error: "queue is full" }, status: :service_unavailable
    end
  end

  def check_requested_fields
    fields_service = Fields::Submission.new(params[:fields])
    render json: { error: "invalid fields: [#{fields_service.invalid_fields.join(", ")}]" }, status: :bad_request if fields_service.has_invalid_fields?
    @requested_fields = fields_service.requested_fields
  end

  def set_base64_encoded
    if Config::DISABLE_IMPLICIT_BASE64_ENCODING
      @base64_encoded = params[:base64_encoded] == "true"
    else
      @base64_encoded = params[:base64_encoded] != "false"
    end
  end

  def render_conversion_error(status, token = nil)
    response_json = {
      error: "some attributes for this submission cannot be converted to UTF-8, use base64_encoded=true query parameter",
    }
    response_json[:token] = token if token

    render json: response_json, status: status
  end
end