Description:
[web] added configurations git-svn-id: http://theory.cpe.ku.ac.th/grader/web/trunk@156 6386c4cd-e34a-4fa8-8920-d93eb39b512e
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r76:d35e4fe536bb - - 16 files changed: 282 inserted, 13 deleted

@@ -0,0 +1,20
1 + class ConfigurationsController < ApplicationController
2 +
3 + before_filter :authenticate
4 + before_filter { |controller| controller.authorization_by_roles(['admin'])}
5 +
6 + in_place_edit_for :configuration, :key
7 + in_place_edit_for :configuration, :type
8 + in_place_edit_for :configuration, :value
9 +
10 + def index
11 + @configurations = Configuration.find(:all,
12 + :order => '`key`')
13 + end
14 +
15 + def reload
16 + Configuration.reload
17 + redirect_to :action => 'index'
18 + end
19 +
20 + end
@@ -0,0 +1,2
1 + module ConfigurationsHelper
2 + end
@@ -0,0 +1,43
1 + class Configuration < ActiveRecord::Base
2 +
3 + @@configurations = nil
4 +
5 + def self.get(key)
6 + if @@configurations == nil
7 + self.read_config
8 + end
9 + return @@configurations[key]
10 + end
11 +
12 + def self.[](key)
13 + self.get(key)
14 + end
15 +
16 + def self.reload
17 + self.read_config
18 + end
19 +
20 + def self.clear
21 + @@configurations = nil
22 + end
23 +
24 + protected
25 + def self.read_config
26 + @@configurations = {}
27 + Configuration.find(:all).each do |conf|
28 + key = conf.key
29 + val = conf.value
30 + case conf.value_type
31 + when 'string'
32 + @@configurations[key] = val
33 +
34 + when 'integer'
35 + @@configurations[key] = val.to_i
36 +
37 + when 'boolean'
38 + @@configurations[key] = (val=='true')
39 + end
40 + end
41 + end
42 +
43 + end
@@ -0,0 +1,26
1 + - content_for :head do
2 + = javascript_include_tag :defaults
3 +
4 + %h1 Grader configuration
5 +
6 + %table
7 + %tr
8 + %th Key
9 + %th Type
10 + %th Value
11 +
12 + - @configurations.each do |conf|
13 + - @configuration = conf
14 + %tr
15 + %td
16 + = in_place_editor_field :configuration, :key, {}, :rows=>1
17 + %td
18 + = in_place_editor_field :configuration, :value_type, {}, :rows=>1
19 + %td
20 + = in_place_editor_field :configuration, :value, {}, :rows=>1
21 +
22 + %br/
23 + = link_to '[Reload configuration]', :action => 'reload'
24 + %br/
25 + Your config is saved, but it does not automatically take effect.
26 + You must reload.
@@ -0,0 +1,37
1 + class CreateConfigurations < ActiveRecord::Migration
2 + def self.up
3 + create_table :configurations do |t|
4 + t.column :key, :string
5 + t.column :value_type, :string
6 + t.column :value, :string
7 + t.timestamps
8 + end
9 +
10 + Configuration.reset_column_information
11 +
12 + Configuration.create(:key => 'system.single_user_mode',
13 + :value_type => 'boolean',
14 + :value => 'false')
15 +
16 + Configuration.create(:key => 'ui.front.title',
17 + :value_type => 'string',
18 + :value => 'Grader')
19 +
20 + Configuration.create(:key => 'ui.front.welcome_message',
21 + :value_type => 'string',
22 + :value => 'Welcome!')
23 +
24 + Configuration.create(:key => 'ui.show_score',
25 + :value_type => 'boolean',
26 + :value => 'true')
27 +
28 + Configuration.create(:key => 'contest.time_limit',
29 + :value_type => 'string',
30 + :value => 'unlimited')
31 +
32 + end
33 +
34 + def self.down
35 + drop_table :configurations
36 + end
37 + end
@@ -0,0 +1,90
1 +
2 + require File.dirname(__FILE__) + '/../spec_helper'
3 +
4 + describe Configuration do
5 +
6 + before(:each) do
7 + @int_config = mock(Configuration,
8 + :id => 1,
9 + :key => 'mode',
10 + :value_type => 'integer',
11 + :value => '30')
12 +
13 + @string_config = mock(Configuration,
14 + :id => 2,
15 + :key => 'title',
16 + :value_type => 'string',
17 + :value => 'Hello')
18 +
19 + @boolean_config = mock(Configuration,
20 + :id => 3,
21 + :key => 'single_user_mode',
22 + :value_type => 'boolean',
23 + :value => 'true')
24 + end
25 +
26 + it "should retrieve int config" do
27 + Configuration.should_receive(:find).once.with(:all).
28 + and_return([@int_config, @string_config, @boolean_config])
29 +
30 + Configuration.clear
31 + val = Configuration.get('mode')
32 + val.should == 30
33 + end
34 +
35 + it "should retrieve boolean config" do
36 + Configuration.should_receive(:find).once.with(:all).
37 + and_return([@int_config, @string_config, @boolean_config])
38 +
39 + Configuration.clear
40 + val = Configuration.get('single_user_mode')
41 + val.should == true
42 + end
43 +
44 + it "should retrieve string config" do
45 + Configuration.should_receive(:find).once.with(:all).
46 + and_return([@int_config, @string_config, @boolean_config])
47 +
48 + Configuration.clear
49 + val = Configuration.get('title')
50 + val.should == "Hello"
51 + end
52 +
53 + it "should retrieve config with []" do
54 + Configuration.should_receive(:find).once.with(:all).
55 + and_return([@int_config, @string_config, @boolean_config])
56 +
57 + Configuration.clear
58 + val = Configuration['title']
59 + val.should == "Hello"
60 + end
61 +
62 + it "should read config table once" do
63 + Configuration.should_receive(:find).once.with(:all).
64 + and_return([@int_config, @string_config, @boolean_config])
65 +
66 + Configuration.clear
67 + val = Configuration.get('title')
68 + val.should == "Hello"
69 + val = Configuration.get('single_user_mode')
70 + val.should == true
71 + val = Configuration.get('mode')
72 + val.should == 30
73 + end
74 +
75 + it "should be able to reload config" do
76 + Configuration.should_receive(:find).twice.with(:all).
77 + and_return([@int_config, @string_config, @boolean_config],
78 + [mock(Configuration,
79 + :key => 'title', :value_type => 'string',
80 + :value => 'Goodbye')])
81 +
82 + Configuration.clear
83 + val = Configuration.get('title')
84 + val.should == "Hello"
85 + Configuration.reload
86 + val = Configuration.get('title')
87 + val.should == "Goodbye"
88 + end
89 +
90 + end
@@ -0,0 +1,7
1 + # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 +
3 + # one:
4 + # column: value
5 + #
6 + # two:
7 + # column: value
@@ -0,0 +1,8
1 + require File.dirname(__FILE__) + '/../test_helper'
2 +
3 + class ConfigurationsControllerTest < ActionController::TestCase
4 + # Replace this with your real tests.
5 + def test_truth
6 + assert true
7 + end
8 + end
@@ -0,0 +1,8
1 + require File.dirname(__FILE__) + '/../test_helper'
2 +
3 + class ConfigurationTest < ActiveSupport::TestCase
4 + # Replace this with your real tests.
5 + def test_truth
6 + assert true
7 + end
8 + end
@@ -1,44 +1,56
1 1 # Filters added to this controller apply to all controllers in the application.
2 2 # Likewise, all the methods added will be available for all controllers.
3 3
4 4 class ApplicationController < ActionController::Base
5 5 # Pick a unique cookie name to distinguish our session data from others'
6 6 session :session_key => '_grader_session_id'
7 7
8 + SINGLE_USER_MODE_CONF_KEY = 'system.single_user_mode'
9 +
10 + def authorization_by_roles(allowed_roles)
11 + return false unless authenticate
12 + user = User.find(session[:user_id])
13 + unless user.roles.detect { |role| allowed_roles.member?(role.name) }
14 + flash[:notice] = 'You are not authorized to view the page you requested'
15 + redirect_to :controller => 'main', :action => 'login'
16 + return false
17 + end
18 + end
19 +
8 20 protected
9 21 def authenticate
10 22 unless session[:user_id]
11 23 redirect_to :controller => 'main', :action => 'login'
12 24 return false
13 25 end
14 26
15 27 # check if run in single user mode
16 - if defined?(SINGLE_USER_MODE) and (SINGLE_USER_MODE)
28 + if (Configuration[SINGLE_USER_MODE_CONF_KEY])
17 29 user = User.find(session[:user_id])
18 30 if user==nil or user.login != 'root'
19 31 redirect_to :controller => 'main', :action => 'login'
20 32 return false
21 33 end
22 34 end
23 35
24 36 return true
25 37 end
26 38
27 39 def authorization
28 40 return false unless authenticate
29 41 user = User.find(session[:user_id])
30 42 unless user.roles.detect { |role|
31 43 role.rights.detect{ |right|
32 44 right.controller == self.class.controller_name and
33 - (right.action == 'all' or right.action == action_name)
45 + (right.action == 'all' or right.action == action_name)
34 46 }
35 47 }
36 48 flash[:notice] = 'You are not authorized to view the page you requested'
37 49 #request.env['HTTP_REFERER'] ? (redirect_to :back) : (redirect_to :controller => 'login')
38 50 redirect_to :controller => 'main', :action => 'login'
39 51 return false
40 52 end
41 53 end
42 54
43 55 end
44 56
@@ -1,93 +1,99
1 1 class MainController < ApplicationController
2 2
3 3 before_filter :authenticate, :except => [:index, :login]
4 4
5 5 verify :method => :post, :only => [:submit],
6 6 :redirect_to => { :action => :index }
7 7
8 8
9 9 def index
10 10 redirect_to :action => 'login'
11 11 end
12 12
13 13 def login
14 + saved_notice = flash[:notice]
14 15 reset_session
16 + flash[:notice] = saved_notice
17 +
18 + @title = Configuration['ui.front.title']
19 + @welcome = Configuration['ui.front.welcome_message']
20 +
15 21 render :action => 'login', :layout => 'empty'
16 22 end
17 23
18 24 def list
19 25 prepare_list_information
20 26 end
21 27
22 28 def submit
23 29 @submission = Submission.new(params[:submission])
24 30 @submission.user_id = session[:user_id]
25 31 @submission.language_id = 0
26 32 @submission.source = params['file'].read if params['file']!=''
27 33 @submission.submitted_at = Time.new
28 34 if @submission.valid?
29 35 if @submission.save == false
30 36 flash[:notice] = 'Error saving your submission'
31 37 elsif Task.create(:submission_id => @submission.id,
32 38 :status => Task::STATUS_INQUEUE) == false
33 39 flash[:notice] = 'Error adding your submission to task queue'
34 40 end
35 41 else
36 42 prepare_list_information
37 43 render :action => 'list' and return
38 44 end
39 45 redirect_to :action => 'list'
40 46 end
41 47
42 48 def source
43 49 submission = Submission.find(params[:id])
44 50 if submission.user_id == session[:user_id]
45 51 fname = submission.problem.name + '.' + submission.language.ext
46 52 send_data(submission.source,
47 53 {:filename => fname,
48 54 :type => 'text/plain'})
49 55 else
50 56 flash[:notice] = 'Error viewing source'
51 57 redirect_to :action => 'list'
52 58 end
53 59 end
54 60
55 61 def compiler_msg
56 62 @submission = Submission.find(params[:id])
57 63 if @submission.user_id == session[:user_id]
58 64 render :action => 'compiler_msg', :layout => 'empty'
59 65 else
60 66 flash[:notice] = 'Error viewing source'
61 67 redirect_to :action => 'list'
62 68 end
63 69 end
64 70
65 71 def submission
66 72 @user = User.find(session[:user_id])
67 73 @problems = Problem.find_available_problems
68 74 if params[:id]==nil
69 75 @problem = nil
70 76 @submissions = nil
71 77 else
72 78 @problem = Problem.find_by_name(params[:id])
73 79 @submissions = Submission.find_all_by_user_problem(@user.id, @problem.id)
74 80 end
75 81 end
76 82
77 83 protected
78 84 def prepare_list_information
79 85 @problems = Problem.find_available_problems
80 86 @prob_submissions = Array.new
81 87 @user = User.find(session[:user_id])
82 88 @problems.each do |p|
83 89 sub = Submission.find_last_by_user_and_problem(@user.id,p.id)
84 90 if sub!=nil
85 91 @prob_submissions << { :count => sub.number, :submission => sub }
86 92 else
87 93 @prob_submissions << { :count => 0, :submission => nil }
88 94 end
89 95 end
90 96 end
91 97
92 98 end
93 99
@@ -1,44 +1,49
1 1 # Methods added to this helper will be available to all templates in the application.
2 2 module ApplicationHelper
3 3
4 4 def user_header
5 5 menu_items = ''
6 6 user = User.find(session[:user_id])
7 7
8 8 # main page
9 9 append_to menu_items, '[Main]', 'main', 'list'
10 10 append_to menu_items, '[Submissions]', 'main', 'submission'
11 11 append_to menu_items, '[Test]', 'test', 'index'
12 12
13 13 # admin menu
14 14 if (user!=nil) and (user.admin?)
15 15 append_to menu_items, '[Problem admin]', 'problems', 'index'
16 16 append_to menu_items, '[User admin]', 'user_admin', 'index'
17 17 append_to menu_items, '[User stat]', 'user_admin', 'user_stat'
18 18 end
19 19
20 20 # general options
21 21 append_to menu_items, '[Settings]', 'users', 'index'
22 +
23 + if (user!=nil) and (user.admin?)
24 + append_to menu_items, '[Site config]', 'configurations', 'index'
25 + end
26 +
22 27 append_to menu_items, '[Log out]', 'main', 'login'
23 28
24 29 menu_items
25 30 end
26 31
27 32 def append_to(option,label, controller, action)
28 33 option << ' ' if option!=''
29 34 option << link_to_unless_current(label,
30 35 :controller => controller,
31 36 :action => action)
32 37 end
33 38
34 39 def format_short_time(time)
35 40 now = Time.now
36 41 st = ''
37 42 if (time.yday != now.yday) or
38 43 (time.year != now.year)
39 44 st = time.strftime("%x ")
40 45 end
41 46 st + time.strftime("%X")
42 47 end
43 48
44 49 end
@@ -1,25 +1,25
1 - <h1>Grader</h1>
1 + <h1><%= @title %></h1>
2 2
3 - <b>Welcome back!</b><br/>
3 + <b><%= @welcome %></b><br/>
4 4 Please login to see the problem list.<br/><br/>
5 5
6 6 <% if flash[:notice] %>
7 7 <hr>
8 8 <b><%= flash[:notice] %></b>
9 9 <hr>
10 10 <% end %>
11 11
12 12 <div style="border: solid 1px gray; padding: 2px; background: #f0f0f0;">
13 13 <% form_tag :controller => 'login', :action => 'login' do %>
14 14 <table>
15 15 <tr>
16 16 <td align="right">User name:</td><td><%= text_field_tag 'login' %></td>
17 17 </tr>
18 18 <tr>
19 19 <td align="right">Password:</td><td><%= password_field_tag %></td>
20 20 </tr>
21 21 </table>
22 22 <%= submit_tag 'Login' %>
23 23 <% end %>
24 24 </div>
25 25
@@ -1,67 +1,64
1 1 # Be sure to restart your web server when you modify this file.
2 2
3 3 # Uncomment below to force Rails into production mode when
4 4 # you don't control web/app server and can't set it the proper way
5 5 # ENV['RAILS_ENV'] ||= 'production'
6 6
7 7 # Specifies gem version of Rails to use when vendor/rails is not present
8 8 RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION
9 9
10 10 # Bootstrap the Rails environment, frameworks, and default configuration
11 11 require File.join(File.dirname(__FILE__), 'boot')
12 12
13 13 Rails::Initializer.run do |config|
14 14 # Settings in config/environments/* take precedence over those specified here
15 15
16 16 # Skip frameworks you're not going to use (only works if using vendor/rails)
17 17 # config.frameworks -= [ :action_web_service, :action_mailer ]
18 18
19 19 # Only load the plugins named here, by default all plugins in vendor/plugins are loaded
20 20 # config.plugins = %W( exception_notification ssl_requirement )
21 21
22 22 # Add additional load paths for your own custom dirs
23 23 # config.load_paths += %W( #{RAILS_ROOT}/extras )
24 24
25 25 # Force all environments to use the same logger level
26 26 # (by default production uses :info, the others :debug)
27 27 # config.log_level = :debug
28 28
29 29 # Use the database for sessions instead of the file system
30 30 # (create the session table with 'rake db:sessions:create')
31 31 config.action_controller.session_store = :active_record_store
32 32
33 33 # Use SQL instead of Active Record's schema dumper when creating the test database.
34 34 # This is necessary if your schema can't be completely dumped by the schema dumper,
35 35 # like if you have constraints or database-specific column types
36 36 # config.active_record.schema_format = :sql
37 37
38 38 # Activate observers that should always be running
39 39 # config.active_record.observers = :cacher, :garbage_collector
40 40
41 41 # Make Active Record use UTC-base instead of local time
42 42 config.active_record.default_timezone = :utc
43 43
44 44 # See Rails::Configuration for more options
45 45 end
46 46
47 47 # Add new inflection rules using the following format
48 48 # (all these examples are active by default):
49 49 # Inflector.inflections do |inflect|
50 50 # inflect.plural /^(ox)$/i, '\1en'
51 51 # inflect.singular /^(ox)en/i, '\1'
52 52 # inflect.irregular 'person', 'people'
53 53 # inflect.uncountable %w( fish sheep )
54 54 # end
55 55
56 56 # Add new mime types for use in respond_to blocks:
57 57 # Mime::Type.register "text/richtext", :rtf
58 58 # Mime::Type.register "application/x-mobile", :mobile
59 59
60 60 # Include your application configuration below
61 61
62 62 # These are where inputs and outputs of test requests are stored
63 63 TEST_REQUEST_INPUT_FILE_DIR = RAILS_ROOT + '/data/test_request/input'
64 64 TEST_REQUEST_OUTPUT_FILE_DIR = RAILS_ROOT + '/data/test_request/output'
65 -
66 - # Uncomment this for single user mode (only root is allowed to log in)
67 - # SINGLE_USER_MODE = true
@@ -1,108 +1,116
1 1 # This file is auto-generated from the current state of the database. Instead of editing this file,
2 2 # please use the migrations feature of ActiveRecord to incrementally modify your database, and
3 3 # then regenerate this schema definition.
4 4 #
5 5 # Note that this schema.rb definition is the authoritative source for your database schema. If you need
6 6 # to create the application database on another system, you should be using db:schema:load, not running
7 7 # all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
8 8 # you'll amass, the slower it'll run and the greater likelihood for issues).
9 9 #
10 10 # It's strongly recommended to check this file into your version control system.
11 11
12 - ActiveRecord::Schema.define(:version => 21) do
12 + ActiveRecord::Schema.define(:version => 22) do
13 +
14 + create_table "configurations", :force => true do |t|
15 + t.string "key"
16 + t.string "value_type"
17 + t.string "value"
18 + t.datetime "created_at"
19 + t.datetime "updated_at"
20 + end
13 21
14 22 create_table "grader_processes", :force => true do |t|
15 23 t.string "host", :limit => 20
16 24 t.integer "pid"
17 25 t.string "mode"
18 26 t.boolean "active"
19 27 t.datetime "created_at"
20 28 t.datetime "updated_at"
21 29 t.integer "task_id"
22 30 end
23 31
24 32 add_index "grader_processes", ["host", "pid"], :name => "index_grader_processes_on_ip_and_pid"
25 33
26 34 create_table "languages", :force => true do |t|
27 35 t.string "name", :limit => 10
28 36 t.string "pretty_name"
29 37 t.string "ext", :limit => 10
30 38 end
31 39
32 40 create_table "problems", :force => true do |t|
33 41 t.string "name", :limit => 30
34 42 t.string "full_name"
35 43 t.integer "full_score"
36 44 t.date "date_added"
37 45 t.boolean "available"
38 46 t.string "url"
39 47 end
40 48
41 49 create_table "rights", :force => true do |t|
42 50 t.string "name"
43 51 t.string "controller"
44 52 t.string "action"
45 53 end
46 54
47 55 create_table "rights_roles", :id => false, :force => true do |t|
48 56 t.integer "right_id"
49 57 t.integer "role_id"
50 58 end
51 59
52 60 add_index "rights_roles", ["role_id"], :name => "index_rights_roles_on_role_id"
53 61
54 62 create_table "roles", :force => true do |t|
55 63 t.string "name"
56 64 end
57 65
58 66 create_table "roles_users", :id => false, :force => true do |t|
59 67 t.integer "role_id"
60 68 t.integer "user_id"
61 69 end
62 70
63 71 add_index "roles_users", ["user_id"], :name => "index_roles_users_on_user_id"
64 72
65 73 create_table "sessions", :force => true do |t|
66 74 t.string "session_id"
67 75 t.text "data"
68 76 t.datetime "updated_at"
69 77 end
70 78
71 79 add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
72 80 add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at"
73 81
74 82 create_table "submissions", :force => true do |t|
75 83 t.integer "user_id"
76 84 t.integer "problem_id"
77 85 t.integer "language_id"
78 86 t.text "source"
79 87 t.binary "binary"
80 88 t.datetime "submitted_at"
81 89 t.datetime "compiled_at"
82 90 t.text "compiler_message"
83 91 t.datetime "graded_at"
84 92 t.integer "points"
85 93 t.text "grader_comment"
86 94 t.integer "number"
87 95 end
88 96
89 97 add_index "submissions", ["user_id", "problem_id", "number"], :name => "index_submissions_on_user_id_and_problem_id_and_number", :unique => true
90 98 add_index "submissions", ["user_id", "problem_id"], :name => "index_submissions_on_user_id_and_problem_id"
91 99
92 100 create_table "tasks", :force => true do |t|
93 101 t.integer "submission_id"
94 102 t.datetime "created_at"
95 103 t.integer "status"
96 104 t.datetime "updated_at"
97 105 end
98 106
99 107 create_table "test_requests", :force => true do |t|
100 108 t.integer "user_id"
101 109 t.integer "problem_id"
102 110 t.integer "submission_id"
103 111 t.string "input_file_name"
104 112 t.string "output_file_name"
105 113 t.string "running_stat"
106 114 t.integer "status"
107 115 t.datetime "updated_at"
108 116 t.datetime "submitted_at"
@@ -1,42 +1,42
1 1
2 2 require File.dirname(__FILE__) + '/../spec_helper'
3 3
4 4 describe MainController do
5 5
6 6 before(:each) do
7 7 @problem = mock(Problem, :name => 'test')
8 8 @language = mock(Language, :name => 'cpp', :ext => 'cpp')
9 9 @submission = mock(Submission,
10 10 :id => 1,
11 11 :user_id => 1,
12 12 :problem => @problem,
13 13 :language => @language,
14 14 :source => 'sample source',
15 15 :compiler_message => 'none')
16 16 @user = mock(User, :id => 1, :login => 'john')
17 - Submission.should_receive(:find).with(@user.id.to_s).and_return(@submission)
17 + Submission.should_receive(:find).with(@submission.id.to_s).and_return(@submission)
18 18 end
19 19
20 20 it "should let user sees her own source" do
21 - get 'source', {:id => 1}, {:user_id => 1}
21 + get 'source', {:id => @submission.id}, {:user_id => 1}
22 22 response.should be_success
23 23 end
24 24
25 25 it "should let user sees her own compiler message" do
26 - get 'compiler_msg', {:id => 1}, {:user_id => 1}
26 + get 'compiler_msg', {:id => @submission.id}, {:user_id => 1}
27 27 response.should be_success
28 28 end
29 29
30 30 it "should not let user sees other user's source" do
31 - get 'source', {:id => 1}, {:user_id => 2}
31 + get 'source', {:id => @submission.id}, {:user_id => 2}
32 32 flash[:notice].should =~ /[Ee]rror/
33 33 response.should redirect_to(:action => 'list')
34 34 end
35 35
36 36 it "should not let user sees other user's compiler message" do
37 - get 'compiler_msg', {:id => 1}, {:user_id => 2}
37 + get 'compiler_msg', {:id => @submission.id}, {:user_id => 2}
38 38 flash[:notice].should =~ /[Ee]rror/
39 39 response.should redirect_to(:action => 'list')
40 40 end
41 41
42 42 end
You need to be logged in to leave comments. Login now