scorm_cloud 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +7 -0
  3. data/Rakefile +25 -0
  4. data/bin/scorm_cloud +6 -0
  5. data/features/api_bugs.feature +12 -0
  6. data/features/course_service.feature +19 -0
  7. data/features/debug_service.feature +12 -0
  8. data/features/registration_service.feature +21 -0
  9. data/features/step_definitions/scorm_cloud_steps.rb +184 -0
  10. data/features/support/env.rb +71 -0
  11. data/features/upload_service.feature +9 -0
  12. data/lib/scorm_cloud.rb +36 -0
  13. data/lib/scorm_cloud/base.rb +110 -0
  14. data/lib/scorm_cloud/base_object.rb +23 -0
  15. data/lib/scorm_cloud/base_service.rb +27 -0
  16. data/lib/scorm_cloud/connection.rb +67 -0
  17. data/lib/scorm_cloud/course.rb +13 -0
  18. data/lib/scorm_cloud/course_service.rb +53 -0
  19. data/lib/scorm_cloud/debug_service.rb +25 -0
  20. data/lib/scorm_cloud/dispatch_service.rb +9 -0
  21. data/lib/scorm_cloud/export_service.rb +7 -0
  22. data/lib/scorm_cloud/railtie.rb +13 -0
  23. data/lib/scorm_cloud/registration.rb +19 -0
  24. data/lib/scorm_cloud/registration_service.rb +50 -0
  25. data/lib/scorm_cloud/reporting_service.rb +7 -0
  26. data/lib/scorm_cloud/scorm_rails_helpers.rb +15 -0
  27. data/lib/scorm_cloud/tagging_service.rb +9 -0
  28. data/lib/scorm_cloud/upload_service.rb +37 -0
  29. data/lib/scorm_cloud/version.rb +3 -0
  30. data/readme.textile +53 -0
  31. data/scorm_cloud.gemspec +24 -0
  32. data/spec/apikey_template.rb +3 -0
  33. data/spec/course_service_spec.rb +31 -0
  34. data/spec/debug_service_spec.rb +26 -0
  35. data/spec/dispatch_service_spec.rb +21 -0
  36. data/spec/export_service_spec.rb +13 -0
  37. data/spec/registration_service_spec.rb +28 -0
  38. data/spec/reporting_service_spec.rb +11 -0
  39. data/spec/small_scorm_package.zip +0 -0
  40. data/spec/spec_helper.rb +4 -0
  41. data/spec/tagging_service_spec.rb +20 -0
  42. data/spec/upload_service_spec.rb +18 -0
  43. metadata +139 -0
@@ -0,0 +1,23 @@
1
+ module ScormCloud
2
+ class BaseObject
3
+
4
+ def set_attributes(attributes)
5
+ attributes.each { |k,v| set_attr(k,v) }
6
+ end
7
+
8
+ def set_attr(k,v)
9
+ f = "#{to_underscore(k)}=".to_sym
10
+ if respond_to?(f)
11
+ send(f, v)
12
+ else
13
+ raise "Object #{self.class} does not respond to #{to_underscore(k)}"
14
+ end
15
+ end
16
+
17
+ def to_underscore(s)
18
+ s.gsub(/(.)([A-Z])/,'\1_\2').downcase
19
+ end
20
+
21
+ end
22
+ end
23
+
@@ -0,0 +1,27 @@
1
+ module ScormCloud
2
+ class BaseService
3
+
4
+ def initialize(connection)
5
+ @connection = connection
6
+ end
7
+
8
+ def connection
9
+ @connection
10
+ end
11
+
12
+ def self.not_implemented(*methods)
13
+ methods.each do |method|
14
+ define_method(method) { raise "Not Implemented: #{method.to_s}" }
15
+ end
16
+ end
17
+
18
+ # Convert xml attributes to hash { :name => value }
19
+ def xml_to_attributes(xml)
20
+ xml.elements["/rsp/attributes"].inject({}) { |h,e|
21
+ h[e.attributes["name"].to_sym] = e.attributes["value"]
22
+ h
23
+ }
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,67 @@
1
+ module ScormCloud
2
+ class Base
3
+
4
+ def initialize(appid, secret)
5
+ @appid = appid
6
+ @secret = secret
7
+ end
8
+
9
+ def call(method, params = {})
10
+ url = prepare_url(method, params)
11
+ execute_call_xml(url)
12
+ end
13
+
14
+ def call_raw(method, params = {})
15
+ url = prepare_url(method, params)
16
+ execute_call_plain(url)
17
+ end
18
+
19
+ # Get plain response body and parse the XML doc
20
+ def execute_call_xml(url)
21
+ doc = REXML::Document.new(execute_call_plain(url))
22
+ raise create_error(doc) unless doc.elements["rsp"].attributes["stat"] == "ok"
23
+ doc
24
+ end
25
+
26
+ # Execute the call - returns response body or redirect url
27
+ def execute_call_plain(url)
28
+ res = Net::HTTP.get_response(URI.parse(url))
29
+ case res
30
+ when Net::HTTPRedirection
31
+ # Return the new URL
32
+ res['location']
33
+ when Net::HTTPSuccess
34
+ res.body
35
+ else
36
+ raise "HTTP Error connecting to scorm cloud: #{res.inspect}"
37
+ end
38
+ end
39
+
40
+ # Get the URL for the call
41
+ def prepare_url(method, params = {})
42
+ timestamp = Time.now.utc.strftime('%Y%m%d%H%M%S')
43
+ params[:method] = method
44
+ params[:appid] = @appid
45
+ params[:ts] = timestamp
46
+ html_params = params.map { |k,v| "#{k.to_s}=#{v}" }.join("&")
47
+
48
+ raw = @secret + params.keys.
49
+ sort{ |a,b| a.to_s.downcase <=> b.to_s.downcase }.
50
+ map{ |k| "#{k.to_s}#{params[k]}" }.
51
+ join
52
+
53
+ sig = Digest::MD5.hexdigest(raw)
54
+ "http://cloud.scorm.com/api?#{html_params}&sig=#{sig}"
55
+ end
56
+
57
+
58
+ # Create an exception with code & message
59
+ def create_error(doc)
60
+ err = doc.elements["rsp"].elements["err"]
61
+ code = err.attributes["code"]
62
+ msg = err.attributes["msg"]
63
+ "Error In Scorm Cloud: Error=#{code} Message=#{msg}"
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,13 @@
1
+ module ScormCloud
2
+ class Course < ScormCloud::BaseObject
3
+
4
+ attr_accessor :id, :versions, :registrations, :title, :size
5
+
6
+ def self.from_xml(element)
7
+ c = Course.new
8
+ c.set_attributes(element.attributes)
9
+ c
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,53 @@
1
+ module ScormCloud
2
+ class CourseService < BaseService
3
+
4
+ not_implemented :import_cours_async, :get_async_import_result,
5
+ :properties, :get_assets, :update_assets,
6
+ :get_file_structure, :delete_files, :get_metadata
7
+
8
+ # TODO: Handle Warnings
9
+ def import_course(course_id, path)
10
+ xml = connection.call("rustici.course.importCourse", :courseid => course_id, :path => path)
11
+ if xml.elements['//rsp/importresult'].attributes["successful"] == "true"
12
+ title = xml.elements['//rsp/importresult/title'].text
13
+ { :title => title, :warnings => [] }
14
+ else
15
+ nil
16
+ end
17
+ end
18
+
19
+ def exists(course_id)
20
+ connection.call_raw("rustici.course.exists", :courseid => course_id).include?("<result>true</result>")
21
+ end
22
+
23
+ def get_attributes(course_id)
24
+ xml = connection.call("rustici.course.getAttributes", :courseid => course_id)
25
+ xml_to_attributes(xml)
26
+ end
27
+
28
+ def delete_course(course_id)
29
+ connection.call("rustici.course.deleteCourse", :courseid => course_id)
30
+ true
31
+ end
32
+
33
+ def get_manifest(course_id)
34
+ connection.call_raw("rustici.course.getManifest", :courseid => course_id)
35
+ end
36
+
37
+ def get_course_list(options = {})
38
+ xml = connection.call("rustici.course.getCourseList", options)
39
+ xml.elements["/rsp/courselist"].map { |e| Course.from_xml(e) }
40
+ end
41
+
42
+ def preview(course_id, redirect_url)
43
+ connection.launch_url("rustici.course.preview", :courseid => course_id, :redirecturl => redirect_url)
44
+ end
45
+
46
+ def update_attributes(course_id, attributes)
47
+ xml = connection.call("rustici.course.updateAttributes", attributes.merge({:courseid => course_id}))
48
+ xml_to_attributes(xml)
49
+ end
50
+
51
+ end
52
+ end
53
+
@@ -0,0 +1,25 @@
1
+ module ScormCloud
2
+ class DebugService < BaseService
3
+
4
+ def ping()
5
+ url = "http://cloud.scorm.com/api?method=rustici.debug.ping"
6
+ data = connection.call_url(url)
7
+ raise "Bad Server Response" unless data.include?("pong")
8
+ "pong"
9
+ end
10
+
11
+ def auth_ping()
12
+ xml = connection.call("rustici.debug.authPing")
13
+ raise "Bad Server Response" unless xml.elements["/rsp/pong"]
14
+ "pong"
15
+ end
16
+
17
+ def get_time()
18
+ xml = connection.call("rustici.debug.getTime")
19
+ time = xml.elements["//currenttime"]
20
+ raise "Bad Server Response" unless time
21
+ time.text
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ module ScormCloud
2
+ class DispatchService < BaseService
3
+
4
+ not_implemented :get_destination_list, :get_destination_info, :create_destination,
5
+ :update_destination, :delete_destination, :get_dispatch_list, :get_dispatch_info,
6
+ :create_dispatch, :update_dispatches, :download_dispatches, :delete_dispatches
7
+
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module ScormCloud
2
+ class ExportService < BaseService
3
+
4
+ not_implemented :start, :cancel, :status, :download, :list
5
+
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ require 'rails'
2
+ require 'scorm_cloud/scorm_rails_helpers'
3
+
4
+ class ScormCloud::Railtie < Rails::Railtie
5
+
6
+ config.scorm_cloud = ActiveSupport::OrderedOptions.new
7
+
8
+ initializer "scorm_cloud.scorm_rails_helpers" do
9
+ ActionController::Base.send :include, ScormCloud::ScormRailsHelpers
10
+ end
11
+
12
+ end
13
+
@@ -0,0 +1,19 @@
1
+ module ScormCloud
2
+ class Registration < ScormCloud::BaseObject
3
+
4
+ attr_accessor :id, :courseid, :app_id, :registration_id, :course_id,
5
+ :course_title, :learner_id, :learner_first_name, :learner_last_name,
6
+ :email, :create_date, :first_access_date, :last_access_date,
7
+ :completed_date, :instances
8
+
9
+ def self.from_xml(element)
10
+ r = Registration.new
11
+ r.set_attributes(element.attributes)
12
+ element.children.each do |element|
13
+ r.set_attr(element.name, element.text)
14
+ end
15
+ r
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,50 @@
1
+ module ScormCloud
2
+ class RegistrationService < BaseService
3
+
4
+ not_implemented :get_registration_list_results,
5
+ :get_launch_history, :get_launch_info, :reset_global_objectives,
6
+ :update_learner_info, :test_registration_post_url
7
+
8
+ def create_registration(course_id, reg_id, first_name, last_name, learner_id, options = {})
9
+ params = options.merge({
10
+ :courseid => course_id,
11
+ :regid => reg_id,
12
+ :fname => first_name,
13
+ :lname => last_name,
14
+ :learnerid => learner_id
15
+ })
16
+ xml = connection.call("rustici.registration.createRegistration", params)
17
+ !xml.elements["/rsp/success"].nil?
18
+ end
19
+
20
+ def delete_registration(reg_id)
21
+ xml = connection.call("rustici.registration.deleteRegistration", {:regid => reg_id })
22
+ !xml.elements["/rsp/success"].nil?
23
+ end
24
+
25
+ def get_registration_list(options = {})
26
+ xml = connection.call("rustici.registration.getRegistrationList", options)
27
+ xml.elements["/rsp/registrationlist"].map { |e| Registration.from_xml(e) }
28
+ end
29
+
30
+ def get_registration_result(reg_id, format="course")
31
+ raise "Illegal format argument: #{format}" unless ["course","activity","full"].include?(format)
32
+ connection.call_raw("rustici.registration.getRegistrationResult", { :regid => reg_id, :format => format })
33
+ end
34
+
35
+ def launch(reg_id, redirect_url, options = {})
36
+ params = options.merge({
37
+ :regid => reg_id,
38
+ :redirecturl => redirect_url
39
+ })
40
+ connection.launch_url("rustici.registration.launch", params)
41
+ end
42
+
43
+ def reset_registration(reg_id)
44
+ xml = connection.call("rustici.registration.resetRegistration", {:regid => reg_id })
45
+ !xml.elements["/rsp/success"].nil?
46
+ end
47
+
48
+
49
+ end
50
+ end
@@ -0,0 +1,7 @@
1
+ module ScormCloud
2
+ class ReportingService < BaseService
3
+
4
+ not_implemented :get_account_info, :get_reportage_auth, :launch_report
5
+
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ module ScormCloud
2
+ module ScormRailsHelpers
3
+
4
+ def scorm_cloud
5
+ unless @scorm_cloud
6
+ @scorm_cloud = ::ScormCloud::ScormCloud.new(
7
+ ::Rails.configuration.scorm_cloud.appid,
8
+ ::Rails.configuration.scorm_cloud.secretkey
9
+ )
10
+ end
11
+ @scorm_cloud
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module ScormCloud
2
+ class TaggingService < BaseService
3
+ not_implemented :get_course_tags, :set_course_tags, :add_course_tag,
4
+ :remove_course_tag, :get_learner_tags, :set_learner_tags, :add_learner_tag,
5
+ :remove_learner_tag, :get_registration_tags, :set_registration_tags,
6
+ :add_registration_tag, :remove_registration_tag
7
+ end
8
+ end
9
+
@@ -0,0 +1,37 @@
1
+ module ScormCloud
2
+ class UploadService < BaseService
3
+
4
+ not_implemented :get_upload_progress
5
+
6
+ def get_upload_token
7
+ xml = connection.call("rustici.upload.getUploadToken")
8
+ xml.elements["/rsp/token/id"].text
9
+ end
10
+
11
+ def upload_file(token, path)
12
+ xml = connection.post("rustici.upload.uploadFile", path, :token => token)
13
+ xml.elements["/rsp/location"].text
14
+ end
15
+
16
+ def list_files
17
+ xml = connection.call("rustici.upload.listFiles")
18
+ xml.elements["//rsp/dir"].map do |f|
19
+ {
20
+ :dir => f.parent.attributes["name"],
21
+ :file => f.attributes["name"],
22
+ :modified => f.attributes["modified"],
23
+ :size => f.attributes["modified"].to_i
24
+ }
25
+ end
26
+ end
27
+
28
+ def delete_files files
29
+ if !files.is_a?(String) && files.is_a?(Enumerable)
30
+ files = files.map { |f| "file=#{f}"}.join('&')
31
+ end
32
+ result = connection.call("rustici.upload.deleteFiles", { :file => files })
33
+ !result.to_s.include?("deleted='false'")
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module ScormCloud
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,53 @@
1
+ h1. Rustici SCORM Cloud Ruby Client
2
+
3
+ This ruby gem is provides a ruby interface to the Rustici Scorm Cloud.
4
+
5
+ h2. Shell CLI Interface
6
+
7
+ $ gem install 'scorm_cloud'
8
+ $ scorm_cloud rustici.course.getCourseList --appid myappid --secret mysecret
9
+
10
+ h2. Standard Ruby Use
11
+
12
+ bc. require 'scorm_cloud'
13
+ sc = ScormCloud::ScormCloud.new("my_app_id","my_secret_key")
14
+ sc.course.get_course_list.each { |c| puts "#{c.id} #{c.title}"}
15
+
16
+ h2. Ruby on Rails Use
17
+
18
+ _Place the following in: Gemfile_
19
+
20
+ bc. require 'scorm_cloud', :git => 'git@github.com:aeseducation/scorm-cloud.git'
21
+
22
+ _Place the following in: config/initializers/scorm_cloud.rb_
23
+
24
+ bc. # Change MyApplication to the name of your application
25
+ MyApplication::Application.configure do |config|
26
+ config.scorm_cloud.appid = "my_app_id"
27
+ config.scorm_cloud.secretkey = "my_secret_key"
28
+ end
29
+
30
+ _Place the following in: /app/controllers.course_controller.rb_
31
+
32
+ bc. # app/controllers/course_controller.rb
33
+ class CourseController < ApplicationController
34
+ def index
35
+ @courses = scorm_cloud.course.get_course_list
36
+ end
37
+ def launch
38
+ return_url = course_index_url
39
+ reg = scorm_cloud.registrations.create_registration(...)
40
+ redirect_to scorm_cloud.registrations.launch(...)
41
+ end
42
+ end
43
+
44
+ _Place the following in: /app/views/course/index.html.erb_
45
+
46
+ bq. # app/views/course/index.html.erb
47
+ &lt;ul&gt;
48
+ &lt;%= @courses.each |course| %&gt;
49
+ &nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;
50
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;%= link_to course_launch_path(course.title,course.id) %&gt;
51
+ &nbsp;&nbsp;&nbsp;&nbsp;&lt;/li&gt;
52
+ &lt;% end %&gt;
53
+ &lt;/ul&gt;