scorm_cloud 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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;