Class: Client

Inherits:
Object
  • Object
show all
Defined in:
lib/tmc-client/client.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(output = $stdout, input = $stdin) ⇒ Client

Returns a new instance of Client.



14
15
16
17
18
19
# File 'lib/tmc-client/client.rb', line 14

def initialize(output=$stdout, input=$stdin)
  @config = MyConfig.new
  @output = output
  @input = input
  setup_client
end

Instance Attribute Details

#configObject

Returns the value of attribute config.



13
14
15
# File 'lib/tmc-client/client.rb', line 13

def config
  @config
end

#connObject

Returns the value of attribute conn.



13
14
15
# File 'lib/tmc-client/client.rb', line 13

def conn
  @conn
end

#coursesObject

Returns the value of attribute courses.



13
14
15
# File 'lib/tmc-client/client.rb', line 13

def courses
  @courses
end

#inputObject

Returns the value of attribute input.



13
14
15
# File 'lib/tmc-client/client.rb', line 13

def input
  @input
end

#outputObject

Returns the value of attribute output.



13
14
15
# File 'lib/tmc-client/client.rb', line 13

def output
  @output
end

Instance Method Details

#authObject



75
76
77
78
79
80
81
82
# File 'lib/tmc-client/client.rb', line 75

def auth
  output.print "Username: "
  username = @input.gets.chomp.strip
  password = get_password("Password (typing is hidden): ")
  @config.auth = nil
  @conn.basic_auth(username, password)
  @config.auth = @conn.headers[Faraday::Request::Authorization::KEY]
end

#checkObject



56
57
58
# File 'lib/tmc-client/client.rb', line 56

def check
  output.puts get_courses_json
end

#current_directory_nameObject



129
130
131
# File 'lib/tmc-client/client.rb', line 129

def current_directory_name
  File.basename(Dir.getwd)
end

#download(*args) ⇒ Object



149
150
151
152
153
154
155
# File 'lib/tmc-client/client.rb', line 149

def download(*args)
  if args.include? "all" or args.include? "-a" or args.include? "--all"
    download_new_exercises
  else
    download_new_exercise(*args)
  end
end

#download_new_exercise(exercise_dir_name = nil) ⇒ Object



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/tmc-client/client.rb', line 272

def download_new_exercise(exercise_dir_name=nil)
  # Initialize course and exercise names to identify exercise to submit (from json)
  course_dir_name = current_directory_name
  exercise_dir_name.chomp("/")
  # Find course and exercise ids
  course = @courses['courses'].select { |course| course['name'] == course_dir_name }.first
  raise "Invalid course name" if course.nil?
  exercise = course["exercises"].select { |ex| ex["name"] == exercise_dir_name }.first
  raise "Invalid exercise name" if exercise.nil?

  raise "Exercise already downloaded" if File.exists? exercise['name']
  zip = fetch_zip(exercise['zip_url'])
  File.open("tmp.zip", 'wb') {|file| file.write(zip.body)}
  full_path = File.join(Dir.pwd, 'tmp.zip')
  unzip_file(full_path, Dir.pwd, exercise_dir_name)
  #`unzip -n tmp.zip && rm tmp.zip`
  `rm tmp.zip`
end

#download_new_exercises(course_dir_name = nil) ⇒ Object



260
261
262
263
264
265
266
267
268
269
270
# File 'lib/tmc-client/client.rb', line 260

def download_new_exercises(course_dir_name=nil)
  course_dir_name = current_directory_name if course_dir_name.nil?
  course = @courses['courses'].select { |course| course['name'] == course_dir_name }.first
  raise "Invalid course name" if course.nil?
  filter_returnable(course['exercises']).each do |ex_name|
    begin
      download_new_exercise(ex_name)
    rescue
    end
  end
end

#fetch_zip(zip_url) ⇒ Object



94
95
96
# File 'lib/tmc-client/client.rb', line 94

def fetch_zip(zip_url)
  @conn.get(zip_url)
end

#filter_returnable(exercises) ⇒ Object



256
257
258
# File 'lib/tmc-client/client.rb', line 256

def filter_returnable(exercises)
  exercises.collect { |ex| ex['name'] if ex['returnable'] }.compact
end

#get(*args) ⇒ Object



39
40
41
42
43
44
45
# File 'lib/tmc-client/client.rb', line 39

def get(*args)
  target = args.first
  case target
    when 'url' then output.puts @config.server_url
    else output.puts "No property selected"
  end
end

#get_course_nameObject



98
99
100
# File 'lib/tmc-client/client.rb', line 98

def get_course_name
  get_my_course['name']
end

#get_courses_jsonObject



60
61
62
63
64
# File 'lib/tmc-client/client.rb', line 60

def get_courses_json
  data = @conn.get('courses.json', {api_version: 5}).body
  raise "Error with autentikation" if data['error']
  data
end

#get_password(prompt = "Enter Password") ⇒ Object



71
72
73
# File 'lib/tmc-client/client.rb', line 71

def get_password(prompt="Enter Password")
   ask(prompt) {|q| q.echo = false}
end

#get_real_name(headers) ⇒ Object



89
90
91
92
# File 'lib/tmc-client/client.rb', line 89

def get_real_name(headers)
  name = headers['content-disposition']
  name.split("\"").compact[-1]
end

#init_connectionObject

stupid name - this should create connection, but not fetch courses.json!



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/tmc-client/client.rb', line 22

def init_connection()
  @conn = Faraday.new(:url => @config.server_url) do |faraday|
    faraday.request  :multipart
    faraday.request  :url_encoded             # form-encode POST params
    #faraday.response :logger                  # log requests to STDOUT We dont want to do this in production!
    faraday.adapter  Faraday.default_adapter  # make requests with Net::HTTP
  end

  if @config.auth
    @conn.headers[Faraday::Request::Authorization::KEY] = @config.auth
  else
    auth
    @conn.headers[Faraday::Request::Authorization::KEY] = @config.auth
  end

end

#init_course(course_name) ⇒ Object



139
140
141
142
143
144
145
146
147
# File 'lib/tmc-client/client.rb', line 139

def init_course(course_name)
  FileUtils.mkdir_p(course_name)
  output.print "Would you like to download all available exercises? Yn"
  if ["", "y", "Y"].include? @input.gets.strip.chomp
    Dir.chdir(course_name) do
      download_new_exercises
    end
  end
end

#is_universal_project?(path) ⇒ Boolean

Path can be relative or absolute

Returns:

  • (Boolean)


67
68
69
# File 'lib/tmc-client/client.rb', line 67

def is_universal_project?(path)
  File.exists? File.join(path, ".universal")
end

#list(mode = :courses) ⇒ Object



102
103
104
105
106
107
108
109
110
111
# File 'lib/tmc-client/client.rb', line 102

def list(mode=:courses)
  mode = mode.to_sym
  case mode
    when :courses
      list_courses
    when :exercises
      list_exercises
    else
  end
end

#list_coursesObject



122
123
124
125
126
127
# File 'lib/tmc-client/client.rb', line 122

def list_courses
  output.puts "No courses available. Make sure your server url is correct and that you have authenticated." if @courses.nil? or @courses["courses"].nil?
  @courses['courses'].each do |course|
    output.puts "#{course['name']}"
  end
end

#list_exercises(course_dir_name = nil) ⇒ Object



113
114
115
116
117
118
119
120
# File 'lib/tmc-client/client.rb', line 113

def list_exercises(course_dir_name=nil)
  course_dir_name = current_directory_name if course_dir_name.nil?
  course = @courses['courses'].select { |course| course['name'] == course_dir_name }.first
  raise "Invalid course name" if course.nil?
  course["exercises"].each do |ex|
    output.puts "#{ex['name']} #{ex['deadline']}" if ex["returnable"]
  end
end

#previous_directory_nameObject



133
134
135
136
137
# File 'lib/tmc-client/client.rb', line 133

def previous_directory_name
  path = Dir.getwd
  directories = path.split("/")
  directories[directories.count - 2]
end

#request_server_urlObject



84
85
86
87
# File 'lib/tmc-client/client.rb', line 84

def request_server_url
  output.print "Server url: "
  @config.server_url = @input.gets.chomp.strip
end

#set(*args) ⇒ Object



47
48
49
50
51
52
53
54
# File 'lib/tmc-client/client.rb', line 47

def set(*args)
  target = args.first
  case target
    when "url"
      @config.server_url = args[1] if args.count >= 2
    else output.puts "No property selected"
  end
end

#solution(exercise_dir_name = nil) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/tmc-client/client.rb', line 157

def solution(exercise_dir_name=nil)
  # Initialize course and exercise names to identify exercise to submit (from json)
  if exercise_dir_name.nil?
    exercise_dir_name = current_directory_name
    course_dir_name = previous_directory_name
  else
    course_dir_name = current_directory_name
  end

  exercise_dir_name.chomp("/")
  # Find course and exercise ids
  course = @courses['courses'].select { |course| course['name'] == course_dir_name }.first
  raise "Invalid course name" if course.nil?
  exercise = course["exercises"].select { |ex| ex["name"] == exercise_dir_name }.first
  raise "Invalid exercise name" if exercise.nil?
  
  update_from_zip(exercise['solution_zip_url'], exercise_dir_name, course_dir_name, exercise, course)
end

#status(submission_id_or_url) ⇒ Object



356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/tmc-client/client.rb', line 356

def status(submission_id_or_url)
  url = (submission_id_or_url.include? "submissions") ? submission_id_or_url : "/submissions/#{submission_id_or_url}.json?api_version=5"
  json = JSON.parse(@conn.get(url).body)
  if json['status'] != 'processing'
    puts "Status: #{json['status']}"
    puts "Points: #{json['points'].inspect}"
    puts "Tests:"
    json['test_cases'].each do |test|
      puts "#{test['name']} : #{(test['successful']) ? 'Ok' : 'Fail'}#{(test['message'].nil?) ? '' : (' : ' + test['message'])}"
    end
  end
  json['status']
end

#submit_exercise(*args) ⇒ Object

Call in exercise root Zipping to stdout zip -r -q - tmc



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
# File 'lib/tmc-client/client.rb', line 310

def submit_exercise(*args)
  # Initialize course and exercise names to identify exercise to submit (from json)
  if args.count == 0 or args.all? { |arg| arg.start_with? "-" }
    exercise_dir_name = current_directory_name
    course_dir_name = previous_directory_name
    zip_file_content(".")
  else
    exercise_dir_name = args.first
    course_dir_name = current_directory_name
    zip_file_content(exercise_dir_name)
  end

  exercise_dir_name.chomp("/")
  exercise_id = 0
  # Find course and exercise ids
  course = @courses['courses'].select { |course| course['name'] == course_dir_name }.first
  raise "Invalid course name" if course.nil?
  exercise = course["exercises"].select { |ex| ex["name"] == exercise_dir_name }.first
  raise "Invalid exercise name" if exercise.nil?

  # Submit
  payload={:submission => {}}
  payload[:request_review] = true if args.include? "--request-review" or args.include? "-r" or args.include? "--review"
  payload[:paste] = true if args.include? "--paste" or args.include? "-p" or args.include? "--public"
  payload[:submission][:file] = Faraday::UploadIO.new('tmp_submit.zip', 'application/zip')
  tmp_conn = Faraday.new(:url => exercise['return_url']) do |faraday|
    faraday.request  :multipart
    faraday.request  :url_encoded             # form-encode POST params
    faraday.adapter  Faraday.default_adapter  # make requests with Net::HTTP
  end
  tmp_conn.headers[Faraday::Request::Authorization::KEY] = @config.auth

  response = tmp_conn.post "?api_version=5&client=netbeans_plugin&client_version=1", payload
  submission_url = JSON.parse(response.body)['submission_url']
  puts "Submission url: #{submission_url}"

  if (args & %w{-q --quiet -s --silent}).empty?
    while status(submission_url) == "processing"
      sleep(1)
    end
  end
  
  FileUtils.rm 'tmp_submit.zip'
  payload
end

#unzip_file(file, destination, exercise_dir_name) ⇒ Object



291
292
293
294
295
296
297
298
299
300
# File 'lib/tmc-client/client.rb', line 291

def unzip_file (file, destination, exercise_dir_name)
  Zip::ZipFile.open(file) do |zip_file|
    zip_file.each do |f|
      merged_path = f.name.sub(exercise_dir_name.gsub("-", "/"), "")
      f_path=File.join(destination, exercise_dir_name, merged_path)
      FileUtils.mkdir_p(File.dirname(f_path))
      zip_file.extract(f, f_path) unless File.exist?(f_path)
    end
  end
end

#update_automatically_detected_project_from_zip(zip_url, exercise_dir_name, course_dir_name, exercise, course) ⇒ Object



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
# File 'lib/tmc-client/client.rb', line 220

def update_automatically_detected_project_from_zip(zip_url, exercise_dir_name, course_dir_name, exercise, course)
  zip = fetch_zip(exercise['zip_url'])
  work_dir = Dir.pwd
  to_dir = if Dir.pwd.chomp("/").split("/").last == exercise_dir_name
    work_dir
  else
    File.join(work_dir, exercise_dir_name)
  end
  Dir.mktmpdir do |tmpdir|
    Dir.chdir tmpdir do
      File.open("tmp.zip", 'wb') {|file| file.write(zip.body)}
      # `unzip -n tmp.zip && rm tmp.zip`
      full_path = File.join(Dir.pwd, 'tmp.zip')
      unzip_file(full_path, Dir.pwd, exercise_dir_name)
      `rm tmp.zip`
      files = Dir.glob('**/*')

      files.each do |file|
        next if file == exercise_dir_name or file.to_s.include? "src" or File.directory? file
        begin
          to = File.join(to_dir,file.split("/")[1..-1].join("/"))
          output.puts "copying #{file} to #{to}"
          unless File.directory? to
            FileUtils.mkdir_p(to.split("/")[0..-2].join("/"))
          else
            FileUtils.mkdir_p(to)
          end
          FileUtils.cp_r(file, to)
        rescue ArgumentError => e
         output.puts "An error occurred #{e}"
        end
      end
    end
  end
end

#update_exercise(exercise_dir_name = nil) ⇒ Object



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
# File 'lib/tmc-client/client.rb', line 370

def update_exercise(exercise_dir_name=nil)
  # Initialize course and exercise names to identify exercise to submit (from json)
  is_universal = false
  if exercise_dir_name.nil?
    exercise_dir_name = current_directory_name
    course_dir_name = previous_directory_name
    is_universal = true if File.exists? ".universal"
  else
    course_dir_name = current_directory_name
    is_universal = true if File.exists?(File.join("#{exercise_dir_name}", ".universal"))
  end

  exercise_dir_name.chomp("/")
  # Find course and exercise ids
  course = @courses['courses'].select { |course| course['name'] == course_dir_name }.first
  raise "Invalid course name" if course.nil?
  exercise = course["exercises"].select { |ex| ex["name"] == exercise_dir_name }.first
  raise "Invalid exercise name" if exercise.nil?

  if is_universal
    update_from_zip(exercise['zip_url'], exercise_dir_name, course_dir_name, exercise, course)
  else
    update_automatically_detected_project_from_zip(exercise['zip_url'], exercise_dir_name, course_dir_name, exercise, course)
  end
end

#update_from_zip(zip_url, exercise_dir_name, course_dir_name, exercise, course) ⇒ Object



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
# File 'lib/tmc-client/client.rb', line 176

def update_from_zip(zip_url, exercise_dir_name, course_dir_name, exercise, course)
  zip = fetch_zip(zip_url)
  output.puts "URL: #{zip_url}"
  work_dir = Dir.pwd
  to_dir = if Dir.pwd.chomp("/").split("/").last == exercise_dir_name
    work_dir
  else
    File.join(work_dir, exercise_dir_name)
  end
  Dir.mktmpdir do |tmpdir|
    Dir.chdir tmpdir do
      File.open("tmp.zip", 'wb') {|file| file.write(zip.body)}
      #`unzip -n tmp.zip && rm tmp.zip`
      full_path = File.join(Dir.pwd, 'tmp.zip')
      unzip_file(full_path, Dir.pwd, exercise_dir_name)
      `rm tmp.zip`
      files = Dir.glob('**/*')
      all_selected = false
      files.each do |file|
        next if file == exercise_dir_name or File.directory? file
        output.puts "Want to update #{file}? Yn[A]" unless all_selected
        input = @input.gets.chomp.strip unless all_selected
        all_selected = true if input == "A" or input == "a"
        if all_selected or (["", "y", "Y"].include? input)
          begin
            to = File.join(to_dir,file.split("/")[1..-1].join("/"))
            output.puts "copying #{file} to #{to}"
            unless File.directory? to
              FileUtils.mkdir_p(to.split("/")[0..-2].join("/"))
            else
              FileUtils.mkdir_p(to)
            end
            FileUtils.cp_r(file, to)
          rescue ArgumentError => e
           output.puts "An error occurred #{e}"
          end
        else
          output.puts "Skipping file #{file}"
        end
      end
    end
  end
end

#zip_file_content(filepath) ⇒ Object

Filepath can be either relative or absolute



303
304
305
306
# File 'lib/tmc-client/client.rb', line 303

def zip_file_content(filepath)
  `zip -r -q tmp_submit.zip #{filepath}`
  #`zip -r -q - #{filepath}`
end