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.
- data/.gitignore +8 -0
- data/Gemfile +7 -0
- data/Rakefile +25 -0
- data/bin/scorm_cloud +6 -0
- data/features/api_bugs.feature +12 -0
- data/features/course_service.feature +19 -0
- data/features/debug_service.feature +12 -0
- data/features/registration_service.feature +21 -0
- data/features/step_definitions/scorm_cloud_steps.rb +184 -0
- data/features/support/env.rb +71 -0
- data/features/upload_service.feature +9 -0
- data/lib/scorm_cloud.rb +36 -0
- data/lib/scorm_cloud/base.rb +110 -0
- data/lib/scorm_cloud/base_object.rb +23 -0
- data/lib/scorm_cloud/base_service.rb +27 -0
- data/lib/scorm_cloud/connection.rb +67 -0
- data/lib/scorm_cloud/course.rb +13 -0
- data/lib/scorm_cloud/course_service.rb +53 -0
- data/lib/scorm_cloud/debug_service.rb +25 -0
- data/lib/scorm_cloud/dispatch_service.rb +9 -0
- data/lib/scorm_cloud/export_service.rb +7 -0
- data/lib/scorm_cloud/railtie.rb +13 -0
- data/lib/scorm_cloud/registration.rb +19 -0
- data/lib/scorm_cloud/registration_service.rb +50 -0
- data/lib/scorm_cloud/reporting_service.rb +7 -0
- data/lib/scorm_cloud/scorm_rails_helpers.rb +15 -0
- data/lib/scorm_cloud/tagging_service.rb +9 -0
- data/lib/scorm_cloud/upload_service.rb +37 -0
- data/lib/scorm_cloud/version.rb +3 -0
- data/readme.textile +53 -0
- data/scorm_cloud.gemspec +24 -0
- data/spec/apikey_template.rb +3 -0
- data/spec/course_service_spec.rb +31 -0
- data/spec/debug_service_spec.rb +26 -0
- data/spec/dispatch_service_spec.rb +21 -0
- data/spec/export_service_spec.rb +13 -0
- data/spec/registration_service_spec.rb +28 -0
- data/spec/reporting_service_spec.rb +11 -0
- data/spec/small_scorm_package.zip +0 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/tagging_service_spec.rb +20 -0
- data/spec/upload_service_spec.rb +18 -0
- 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,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,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,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
|
data/readme.textile
ADDED
@@ -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
|
+
<ul>
|
48
|
+
<%= @courses.each |course| %>
|
49
|
+
<li>
|
50
|
+
<%= link_to course_launch_path(course.title,course.id) %>
|
51
|
+
</li>
|
52
|
+
<% end %>
|
53
|
+
</ul>
|