diff --git a/Gemfile.lock b/Gemfile.lock index 166109fe..67860f20 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -443,6 +443,3 @@ DEPENDENCIES uglifier (>= 1.3.0) web-console (~> 2.0) will_paginate (~> 3.0) - -BUNDLED WITH - 1.13.6 diff --git a/app/assets/remote_scripts/client_script.ps1 b/app/assets/remote_scripts/client_script.ps1 new file mode 100644 index 00000000..1ef00a57 --- /dev/null +++ b/app/assets/remote_scripts/client_script.ps1 @@ -0,0 +1,88 @@ +# run like this: powershell.exe -noprofile -executionpolicy bypass -file path\to\client_script.ps1 path\to\project_root + +# CodeOcean Remote Client v0.4 + +#file_info format: = (src/frog.java=34) +#file_path format: + +param ( + [string]$project_root +) +if ( !($project_root | select-string -pattern '[/\\]$') ){ + $project_root += '/' +} + + +function post_web_request ($content_type, $data, $url){ + $buffer = [System.Text.Encoding]::UTF8.GetBytes($data) + [System.Net.HttpWebRequest] $web_request = [System.Net.WebRequest]::Create($url) + $web_request.Method = 'POST' + $web_request.ContentType = $content_type + $web_request.ContentLength = $buffer.Length; + + $request_stream = $web_request.GetRequestStream() + $request_stream.Write($buffer, 0, $buffer.Length) + $request_stream.Flush() + $request_stream.Close() + + [System.Net.HttpWebResponse] $web_response = $web_request.GetResponse() + $stream_reader = new-object System.IO.StreamReader($web_response.GetResponseStream()) + $result = $stream_reader.ReadToEnd() + return $result +} + +function find_file ($file_name){ + $search_result = get-childitem -recurse -path $project_root -filter $file_name + if( !$search_result.exists ){ + write-host "Error: $file_name could not be found under $project_root." + exit 1 + }elseif( $search_result.gettype().name -eq 'Object[]' ){ + $search_result = $search_result[0] + } + return $search_result +} + +function get_file ($file_path){ + $path_to_file = $project_root + $path_to_file += ($file_path | select-string -pattern '^.+/').matches.value + $file_name = ($file_path | select-string -pattern '[^/]+$').matches.value + $file = get-childitem -path $path_to_file -filter $file_name + if( !$file.exists ){ + write-host "Warning: $file_name should be in $path_to_file, but it is not. Searching whole project..." + $file = find_file $file_name + write-host 'Using '$file.fullname'.' + } + return $file +} + +function get_escaped_file_content ($file){ + $content = [IO.File]::ReadAllText($file.fullname) + $content = $content -replace "`r`n", '\n' + $content = $content -replace "`n", '\n' + $content = $content.replace('"', '\"') + return $content +} + +function get_file_attributes ($file_info){ + $file = get_file ($file_info | select-string -pattern '^.*(?==)').matches.value + $escaped_file_content = get_escaped_file_content $file + $file_id = ($file_info | select-string -pattern '[^=]+$').matches.value + return "{`"file_id`": $file_id,`"content`": `"$escaped_file_content`"}" +} + +$co_file = get_file '.co' + +$file_array = get-content $co_file.fullname + +$validation_token = $file_array[0] + +$files_attributes = get_file_attributes $file_array[1] + +for ($i = 2; $i -lt $file_array.length; $i++){ + $files_attributes += ', ' + $files_attributes += get_file_attributes $file_array[$i] +} + +$post_data = "{`"remote_evaluation`": {`"validation_token`": `"$validation_token`",`"files_attributes`": [$files_attributes]}}" + +post_web_request 'application/json' $post_data 'http://codeocean.openhpi.de/evaluate' \ No newline at end of file diff --git a/app/assets/remote_scripts/client_script.sh b/app/assets/remote_scripts/client_script.sh new file mode 100644 index 00000000..8bde2fd0 --- /dev/null +++ b/app/assets/remote_scripts/client_script.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# CodeOcean Remote Client v0.4 + +#file_info format: = (src/frog.java=34) +#file_path format: + + +project_root="${1%/}" + +function get_valid_file_path { + file_path="$project_root/$1" + if [ -e "$file_path" ]; then + valid_file_path="$file_path" + else + file_name="${1##*/}" + valid_file_path="$(find "$project_root" -name "$file_name" | head -1)" + if ! [ "$valid_file_path" ]; then + path_to_file="$(echo "$1" | grep -oP '^.+/')" + echo "Error: $file_name is not in $project_root/$path_to_file and could not be found under $project_root." + exit 1 + fi + fi + echo "$valid_file_path" +} + +function get_escaped_file_content { + file_path="$1" + cat "$file_path" | + perl -p -e 's@\r\n@\\n@g' | + perl -p -e 's@\n@\\n@g' | + perl -p -e 's@"@\\"@g' +} + +function get_file_attributes { + file_info="$1" + file_path="$(get_valid_file_path "${file_info%=*}")" + escaped_file_content="$(get_escaped_file_content "$file_path")" + file_id="${file_info##*=}" + echo "{\"file_id\": $file_id,\"content\": \"$escaped_file_content\"}" +} + + +co_file_path="$(get_valid_file_path '.co')" +mapfile -t file_array < "$co_file_path" + +validation_token="${file_array[0]}" + +files_attributes="$(get_file_attributes "${file_array[1]}")" + +for ((i = 2; i < ${#file_array[@]}; i++)); do + files_attributes+=", $(get_file_attributes "${file_array[i]}")" +done + +post_data="{\"remote_evaluation\": {\"validation_token\": \"$validation_token\",\"files_attributes\": [$files_attributes]}}" + +curl -H 'Content-Type: application/json' --data "$post_data" http://codeocean.openhpi.de/evaluate +echo \ No newline at end of file diff --git a/app/assets/remote_scripts/client_script_osx.sh b/app/assets/remote_scripts/client_script_osx.sh new file mode 100644 index 00000000..955b03bd --- /dev/null +++ b/app/assets/remote_scripts/client_script_osx.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# CodeOcean Remote Client v0.4 + +#file_info format: = (src/frog.java=34) +#file_path format: + + +project_root="${1%/}" +declare -a file_array + +function get_valid_file_path { + file_path="$project_root/$1" + if [ -e "$file_path" ]; then + valid_file_path="$file_path" + else + file_name="${1##*/}" + valid_file_path="$(find "$project_root" -name "$file_name" | head -1)" + if ! [ "$valid_file_path" ]; then + path_to_file="$(echo "$1" | pcregrep -o '^.+/')" + echo "Error: $file_name is not in $project_root/$path_to_file and could not be found under $project_root." + exit 1 + fi + fi + echo "$valid_file_path" +} + +function get_escaped_file_content { + file_path="$1" + cat "$file_path" | + perl -p -e 's@\r\n@\\n@g' | + perl -p -e 's@\n@\\n@g' | + perl -p -e 's@"@\\"@g' +} + +function get_file_attributes { + file_info="$1" + file_path="$(get_valid_file_path "${file_info%=*}")" + escaped_file_content="$(get_escaped_file_content "$file_path")" + file_id="${file_info##*=}" + echo "{\"file_id\": $file_id,\"content\": \"$escaped_file_content\"}" +} + +function read_file_to_array { + let i=0 + while IFS=$'\n' read -r line_data; do + file_array[i]="${line_data}" + ((++i)) + done < $1 +} + + +co_file_path="$(get_valid_file_path '.co')" +read_file_to_array $co_file_path + +validation_token="${file_array[0]}" + +files_attributes="$(get_file_attributes "${file_array[1]}")" + +for ((i = 2; i < ${#file_array[@]}; i++)); do + files_attributes+=", $(get_file_attributes "${file_array[i]}")" +done + +post_data="{\"remote_evaluation\": {\"validation_token\": \"$validation_token\",\"files_attributes\": [$files_attributes]}}" + +#echo ${post_data} +curl -H 'Content-Type: application/json' --data "$post_data" http://codeocean.openhpi.de/evaluate +echo \ No newline at end of file diff --git a/app/controllers/concerns/remote_evaluation_parameters.rb b/app/controllers/concerns/remote_evaluation_parameters.rb new file mode 100644 index 00000000..4f0acab2 --- /dev/null +++ b/app/controllers/concerns/remote_evaluation_parameters.rb @@ -0,0 +1,8 @@ +module RemoteEvaluationParameters + include FileParameters + + def remote_evaluation_params + remote_evaluation_params = params[:remote_evaluation].permit(:validation_token, files_attributes: file_attributes) + end + private :remote_evaluation_params +end \ No newline at end of file diff --git a/app/controllers/remote_evaluation_controller.rb b/app/controllers/remote_evaluation_controller.rb new file mode 100644 index 00000000..36d61f73 --- /dev/null +++ b/app/controllers/remote_evaluation_controller.rb @@ -0,0 +1,35 @@ +class RemoteEvaluationController < ApplicationController + include RemoteEvaluationParameters + include SubmissionScoring + + skip_after_action :verify_authorized + skip_before_action :verify_authenticity_token + + # POST /evaluate + # @param validation_token + # @param files_attributes + def evaluate + + validation_token = remote_evaluation_params[:validation_token] + files_attributes = remote_evaluation_params[:files_attributes] || [] + + # todo extra: validiere, ob files wirklich zur Übung gehören (wenn allowNewFiles-flag nicht gesetzt ist) + if (remote_evaluation_mapping = RemoteEvaluationMapping.find_by(:validation_token => validation_token)) + puts remote_evaluation_mapping.exercise_id + puts remote_evaluation_mapping.user_id + + _params = remote_evaluation_params.except(:validation_token) + _params[:exercise_id] = remote_evaluation_mapping.exercise_id + _params[:user_id] = remote_evaluation_mapping.user_id + _params[:cause] = "remoteAssess" + _params[:user_type] = "ExternalUser" + + @submission = Submission.create(_params) + render json: score_submission(@submission) + else + # todo: better output + # todo: check token expired? + render json: "No exercise found for this validation_token! Please keep out!" + end + end +end \ No newline at end of file diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 16fcd697..182c22c4 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -57,12 +57,29 @@ class SubmissionsController < ApplicationController # files = @submission.files.map{ } # zipline( files, 'submission.zip') # send_data(@file.content, filename: @file.name_with_extension) + + id_file = create_remote_evaluation_mapping + require 'zip' stringio = Zip::OutputStream.write_buffer do |zio| @files.each do |file| - zio.put_next_entry(file.name_with_extension) + zio.put_next_entry(file.path.to_s == '' ? file.name_with_extension : File.join(file.path, file.name_with_extension)) zio.write(file.content) end + + # zip .co file + zio.put_next_entry(File.basename id_file) + zio.write(File.read id_file) + File.delete(id_file) if File.exist?(id_file) + + # zip client scripts + scripts_path = 'app/assets/remote_scripts' + Dir.foreach(scripts_path) do |file| + next if file == '.' or file == '..' + zio.put_next_entry(File.join('.scripts', File.basename(file))) + zio.write(File.read File.join(scripts_path, file)) + end + end send_data(stringio.string, filename: @submission.exercise.title.tr(" ", "_") + ".zip") end @@ -316,4 +333,23 @@ class SubmissionsController < ApplicationController server_sent_event.close end private :with_server_sent_events -end + + def create_remote_evaluation_mapping + user_id = @submission.user_id + exercise_id = @submission.exercise_id + + remote_evaluation_mapping = RemoteEvaluationMapping.create(:user_id => user_id, :exercise_id => exercise_id) + + # create id.co file + path = "tmp/.co" + content = "#{remote_evaluation_mapping.validation_token}\n" + @submission.files.each do |file| + file_path = file.path.to_s == '' ? file.name_with_extension : File.join(file.path, file.name_with_extension) + content += "#{file_path}=#{file.id.to_s}\n" + end + File.open(path, "w+") do |f| + f.write(content) + end + path + end +end \ No newline at end of file diff --git a/app/models/remote_evaluation_mapping.rb b/app/models/remote_evaluation_mapping.rb new file mode 100644 index 00000000..be0034b1 --- /dev/null +++ b/app/models/remote_evaluation_mapping.rb @@ -0,0 +1,10 @@ +# todo: reference to lti_param_model +class RemoteEvaluationMapping < ActiveRecord::Base + before_create :generate_token, unless: :validation_token? + belongs_to :exercise + belongs_to :user + + def generate_token + self.validation_token = SecureRandom.urlsafe_base64 + end +end \ No newline at end of file diff --git a/app/models/submission.rb b/app/models/submission.rb index 1abbbb84..6815eb40 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -2,7 +2,7 @@ class Submission < ActiveRecord::Base include Context include Creation - CAUSES = %w(assess download file render run save submit test autosave requestComments) + CAUSES = %w(assess download file render run save submit test autosave requestComments remoteAssess) FILENAME_URL_PLACEHOLDER = '{filename}' belongs_to :exercise diff --git a/config/routes.rb b/config/routes.rb index b4606f74..6abbe83f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -105,4 +105,6 @@ Rails.application.routes.draw do end end + post "/evaluate", to: 'remote_evaluation#evaluate', via: [:post] + end diff --git a/db/migrate/20170202170437_create_remote_evaluation_mappings.rb b/db/migrate/20170202170437_create_remote_evaluation_mappings.rb new file mode 100644 index 00000000..d102370d --- /dev/null +++ b/db/migrate/20170202170437_create_remote_evaluation_mappings.rb @@ -0,0 +1,11 @@ +class CreateRemoteEvaluationMappings < ActiveRecord::Migration + def change + create_table :remote_evaluation_mappings do |t| + t.integer "user_id", null: false + t.integer "exercise_id", null: false + t.string "validation_token", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + end +end \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index 6f5accb0..e53b502f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -191,6 +191,14 @@ ActiveRecord::Schema.define(version: 20161214144837) do t.datetime "updated_at" end + create_table "remote_evaluation_mappings", force: :cascade do |t| + t.integer "user_id", null: false + t.integer "exercise_id", null: false + t.string "validation_token", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "request_for_comments", force: :cascade do |t| t.integer "user_id", null: false t.integer "exercise_id", null: false