sakai-info 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +12 -0
- data/LICENSE +8 -0
- data/README.md +148 -0
- data/ROADMAP.md +37 -0
- data/bin/sakai-info +79 -0
- data/lib/sakai-info.rb +62 -0
- data/lib/sakai-info/announcement.rb +170 -0
- data/lib/sakai-info/assignment.rb +281 -0
- data/lib/sakai-info/authz.rb +378 -0
- data/lib/sakai-info/cli.rb +35 -0
- data/lib/sakai-info/cli/help.rb +103 -0
- data/lib/sakai-info/configuration.rb +288 -0
- data/lib/sakai-info/content.rb +300 -0
- data/lib/sakai-info/db.rb +19 -0
- data/lib/sakai-info/gradebook.rb +176 -0
- data/lib/sakai-info/group.rb +119 -0
- data/lib/sakai-info/instance.rb +122 -0
- data/lib/sakai-info/message.rb +122 -0
- data/lib/sakai-info/sakai_object.rb +58 -0
- data/lib/sakai-info/sakai_xml_entity.rb +126 -0
- data/lib/sakai-info/samigo.rb +219 -0
- data/lib/sakai-info/site.rb +752 -0
- data/lib/sakai-info/user.rb +220 -0
- data/lib/sakai-info/version.rb +3 -0
- metadata +90 -0
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
This work is dedicated to the public domain. No rights reserved.
|
2
|
+
|
3
|
+
I, the copyright holder of this work, hereby release it into the public
|
4
|
+
domain. This applies worldwide.
|
5
|
+
|
6
|
+
I grant any entity the right to use this work for any purpose, without
|
7
|
+
any conditions, unless such conditions are required by law.
|
8
|
+
|
data/README.md
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
sakai-info
|
2
|
+
==========
|
3
|
+
|
4
|
+
*sakai-info* is a command line tool and a suite of Ruby libraries which enable
|
5
|
+
the exploration of a Sakai database without the intermediation of a Java VM or
|
6
|
+
any official Sakai code.
|
7
|
+
|
8
|
+
Because the primary goal of this tool is to assist in information gathering
|
9
|
+
and troubleshooting, no capability to change the database is included in the
|
10
|
+
tool or the libraries.
|
11
|
+
|
12
|
+
Meta
|
13
|
+
----
|
14
|
+
last updated: 2012-02-19
|
15
|
+
author: David Adams (daveadams@gmail.com)
|
16
|
+
github url: https://github.com/daveadams/sakai-info
|
17
|
+
|
18
|
+
Testing
|
19
|
+
-------
|
20
|
+
|
21
|
+
Tests are defined in ./test using Test::Unit. The default `rake` action is to
|
22
|
+
run all tests.
|
23
|
+
|
24
|
+
Building
|
25
|
+
--------
|
26
|
+
|
27
|
+
Use `rake` to test and build the gem:
|
28
|
+
|
29
|
+
$ rake gem:build
|
30
|
+
|
31
|
+
The resulting gem will be saved to the working directory as
|
32
|
+
`sakai-info-0.1.0.gem`.
|
33
|
+
|
34
|
+
Cleanup built gems using:
|
35
|
+
|
36
|
+
$ rake clean
|
37
|
+
|
38
|
+
Installing
|
39
|
+
----------
|
40
|
+
|
41
|
+
Install the *sakai-info* gem locally with:
|
42
|
+
|
43
|
+
$ rake gem:install
|
44
|
+
|
45
|
+
Uninstall with:
|
46
|
+
|
47
|
+
$ rake gem:uninstall
|
48
|
+
|
49
|
+
Supported Databases
|
50
|
+
-------------------
|
51
|
+
|
52
|
+
For this release, only Oracle databases are supported. MySQL support is planned
|
53
|
+
as soon as possible. Support for SQLite or some other lightweight database may
|
54
|
+
be implemented for unit testing purposes.
|
55
|
+
|
56
|
+
System Requirements
|
57
|
+
-------------------
|
58
|
+
|
59
|
+
For Oracle support, the `ruby-oci8` gem is required.
|
60
|
+
|
61
|
+
So far, testing has occurred using Ruby 1.9.1p376 and Ruby 1.9.2p290, with
|
62
|
+
version 2.0.6 of the `ruby-oci8` gem, on Ubuntu 10.04, against Oracle 11g
|
63
|
+
R2 versions 11.2.0.1 and 11.2.0.3, using the Oracle Instant Client version
|
64
|
+
11.2.0.3.
|
65
|
+
|
66
|
+
Configuration
|
67
|
+
-------------
|
68
|
+
|
69
|
+
To run, *sakai-info* needs to be able to connect to your Sakai database server.
|
70
|
+
It is possible to specify multiple instances to choose from at runtime.
|
71
|
+
|
72
|
+
In this release, *sakai-info* expects a to find the config in a file located at
|
73
|
+
`$HOME/.sakai-info`. The file must be in YAML format and can contain one or
|
74
|
+
more Sakai database connection definitions. To define a single database
|
75
|
+
connection, specify the file like this:
|
76
|
+
|
77
|
+
dbtype: oracle
|
78
|
+
username: sakai
|
79
|
+
password: <password>
|
80
|
+
service: SAKAIPROD
|
81
|
+
host: oracle.db
|
82
|
+
port: 1521
|
83
|
+
|
84
|
+
The `port` value is optional, with a default of 1521. If your Oracle setup uses
|
85
|
+
a `tnsnames.ora` file, then both `host` and `port` can be excluded, and
|
86
|
+
`service` will be used to find the corresponding `tnsnames.ora` entry.
|
87
|
+
|
88
|
+
Multiple instances may be specified by using the following format:
|
89
|
+
|
90
|
+
default: production
|
91
|
+
instances:
|
92
|
+
production:
|
93
|
+
dbtype: oracle
|
94
|
+
username: sakai
|
95
|
+
password: <password>
|
96
|
+
service: SAKAIPROD
|
97
|
+
host: oracle.db
|
98
|
+
port: 1521
|
99
|
+
test:
|
100
|
+
dbtype: oracle
|
101
|
+
username: sakai
|
102
|
+
password: <password>
|
103
|
+
service: SAKAITEST
|
104
|
+
|
105
|
+
The `default` key identifies which of the connections under `instances` you
|
106
|
+
wish to be used in the absence of any explicit specification. Other instances
|
107
|
+
will be referenced by the corresponding YAML key (eg, `production` and `test`
|
108
|
+
in the example above).
|
109
|
+
|
110
|
+
Command Line Usage
|
111
|
+
------------------
|
112
|
+
|
113
|
+
After installing the gem, the `sakai-info` program should be found in your
|
114
|
+
PATH. For this release, very limited functionality is included. For usage
|
115
|
+
details, run:
|
116
|
+
|
117
|
+
$ sakai-info help
|
118
|
+
|
119
|
+
Library Usage
|
120
|
+
-------------
|
121
|
+
|
122
|
+
To use the library in your own Ruby programs, simply specify:
|
123
|
+
|
124
|
+
require 'sakai-info'
|
125
|
+
|
126
|
+
Further specifying `include SakaiInfo` will pull all object classes into the
|
127
|
+
primary namespace and could save some typing. Be careful that class names such
|
128
|
+
as `User`, `Site`, and `Group` do not conflict with other elements of your
|
129
|
+
program.
|
130
|
+
|
131
|
+
Full RDoc documentation for each class is not available in this release, but is
|
132
|
+
planned for a future release.
|
133
|
+
|
134
|
+
Change History
|
135
|
+
--------------
|
136
|
+
|
137
|
+
See CHANGELOG.md
|
138
|
+
|
139
|
+
Future Plans
|
140
|
+
------------
|
141
|
+
|
142
|
+
See ROADMAP.md
|
143
|
+
|
144
|
+
License
|
145
|
+
-------
|
146
|
+
This work is dedicated to the public domain. No rights are reserved. See
|
147
|
+
LICENSE for more information.
|
148
|
+
|
data/ROADMAP.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
sakai-info Roadmap
|
2
|
+
==================
|
3
|
+
|
4
|
+
*Last updated 2012-02-18 by daveadams@gmail.com*
|
5
|
+
|
6
|
+
The most important things to get to after the initial release are:
|
7
|
+
|
8
|
+
* MySQL support
|
9
|
+
* Command-line access to all object types, properties, and relationships
|
10
|
+
understood by the library.
|
11
|
+
* Full RDoc documentation of the library classes, properties, and methods
|
12
|
+
|
13
|
+
Other things on the wishlist for future releases:
|
14
|
+
|
15
|
+
* More complete object relationship support for full drilldown capability
|
16
|
+
* Gradebook/assignment/quiz interaction
|
17
|
+
* Quiz attempts per-student and per-quiz
|
18
|
+
* Assignment submissions per-assignment and per-student
|
19
|
+
* Support for more Sakai objects
|
20
|
+
* OSP
|
21
|
+
* Forum topics and posts
|
22
|
+
* Private messages
|
23
|
+
* Events
|
24
|
+
* Sessions
|
25
|
+
* Generalized reporting capabilities
|
26
|
+
* Storage utilization per-site
|
27
|
+
* Session and event statistics
|
28
|
+
* Metrics on various elements, eg:
|
29
|
+
* Typical quiz duration
|
30
|
+
* Quiz/assignment completion rates
|
31
|
+
* Forum post rates, post length
|
32
|
+
* etc
|
33
|
+
|
34
|
+
The ultimate dream:
|
35
|
+
|
36
|
+
* A web interface for searching and exploring the database
|
37
|
+
|
data/bin/sakai-info
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# sakai-info
|
4
|
+
# Command line tool for exploring the Sakai database via the
|
5
|
+
# sakai-info library
|
6
|
+
#
|
7
|
+
# Created 2012-02-15 daveadams@gmail.com
|
8
|
+
# Last updated 2012-02-19 daveadams@gmail.com
|
9
|
+
#
|
10
|
+
# https://github.com/daveadams/sakai-info
|
11
|
+
#
|
12
|
+
# This software is public domain.
|
13
|
+
#
|
14
|
+
|
15
|
+
require 'sakai-info'
|
16
|
+
include SakaiInfo
|
17
|
+
|
18
|
+
require 'sakai-info/cli'
|
19
|
+
|
20
|
+
ObjectModes = %w(site user)
|
21
|
+
|
22
|
+
if ARGV.length > 0
|
23
|
+
case ARGV[0]
|
24
|
+
|
25
|
+
when "help" then
|
26
|
+
if ARGV.length > 1
|
27
|
+
CLI::Help.help ARGV[1]
|
28
|
+
else
|
29
|
+
CLI::Help.help
|
30
|
+
end
|
31
|
+
exit
|
32
|
+
|
33
|
+
when "version" then
|
34
|
+
puts VERSION
|
35
|
+
exit
|
36
|
+
|
37
|
+
when "validate" then
|
38
|
+
if CLI.validate_config
|
39
|
+
puts "Configuration OK"
|
40
|
+
exit
|
41
|
+
else
|
42
|
+
exit 1
|
43
|
+
end
|
44
|
+
|
45
|
+
when "test" then
|
46
|
+
STDERR.puts "PENDING: test mode unimplemented"
|
47
|
+
exit 1
|
48
|
+
|
49
|
+
else
|
50
|
+
# test to see if it's an accepted object mode
|
51
|
+
if ObjectModes.include? ARGV[0]
|
52
|
+
mode = ARGV[0]
|
53
|
+
ARGV.shift
|
54
|
+
# continue on
|
55
|
+
|
56
|
+
else
|
57
|
+
STDERR.puts "ERROR: Command '#{ARGV[0]}' was not recognized."
|
58
|
+
STDERR.puts "Run 'sakai-info help' for a list of commands."
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
else
|
63
|
+
STDERR.puts "ERROR: No command was given."
|
64
|
+
STDERR.puts "Run 'sakai-info help' for a list of commands."
|
65
|
+
exit 1
|
66
|
+
end
|
67
|
+
|
68
|
+
exit 1 if not CLI.validate_config
|
69
|
+
|
70
|
+
if mode == "user"
|
71
|
+
print "Total users: "; STDOUT.flush
|
72
|
+
puts User.count
|
73
|
+
|
74
|
+
elsif mode == "site"
|
75
|
+
print "Total sites: "; STDOUT.flush
|
76
|
+
puts Site.count
|
77
|
+
|
78
|
+
end
|
79
|
+
|
data/lib/sakai-info.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# sakai-info.rb
|
2
|
+
# Base library file
|
3
|
+
#
|
4
|
+
# Created 2012-02-15 daveadams@gmail.com
|
5
|
+
# Last updated 2012-02-19 daveadams@gmail.com
|
6
|
+
#
|
7
|
+
# https://github.com/daveadams/sakai-info
|
8
|
+
#
|
9
|
+
# This software is public domain.
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'yaml'
|
13
|
+
require 'json'
|
14
|
+
require 'rexml/document'
|
15
|
+
require 'base64'
|
16
|
+
|
17
|
+
require 'sakai-info/version'
|
18
|
+
|
19
|
+
module SakaiInfo
|
20
|
+
# base exception class for distinguishing SakaiInfo exceptions
|
21
|
+
# from Ruby exceptions
|
22
|
+
class SakaiException < Exception; end
|
23
|
+
|
24
|
+
# exception to be raised when an object of a certain type cannot be found
|
25
|
+
class ObjectNotFoundException < SakaiException
|
26
|
+
def initialize(classname, identifier)
|
27
|
+
@classname = classname
|
28
|
+
@identifier = identifier
|
29
|
+
|
30
|
+
super("Could not find a #{@classname} object for '#{@identifier}'")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# extensions to other objects
|
36
|
+
class String
|
37
|
+
def is_uuid?
|
38
|
+
self =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# baseline config and connectivity
|
43
|
+
require 'sakai-info/instance'
|
44
|
+
require 'sakai-info/db'
|
45
|
+
require 'sakai-info/configuration'
|
46
|
+
|
47
|
+
# base objects
|
48
|
+
require 'sakai-info/sakai_object'
|
49
|
+
require 'sakai-info/sakai_xml_entity'
|
50
|
+
|
51
|
+
# sakai object classes
|
52
|
+
require 'sakai-info/user'
|
53
|
+
require 'sakai-info/site'
|
54
|
+
require 'sakai-info/announcement'
|
55
|
+
require 'sakai-info/assignment'
|
56
|
+
require 'sakai-info/authz'
|
57
|
+
require 'sakai-info/content'
|
58
|
+
require 'sakai-info/gradebook'
|
59
|
+
require 'sakai-info/group'
|
60
|
+
require 'sakai-info/message'
|
61
|
+
require 'sakai-info/samigo'
|
62
|
+
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# sakai-info/announcement.rb
|
2
|
+
# SakaiInfo::Announcement library
|
3
|
+
#
|
4
|
+
# Created 2012-02-16 daveadams@gmail.com
|
5
|
+
# Last updated 2012-02-16 daveadams@gmail.com
|
6
|
+
#
|
7
|
+
# https://github.com/daveadams/sakai-info
|
8
|
+
#
|
9
|
+
# This software is public domain.
|
10
|
+
#
|
11
|
+
|
12
|
+
module SakaiInfo
|
13
|
+
class AnnouncementChannel < SakaiXMLEntity
|
14
|
+
attr_reader :next
|
15
|
+
|
16
|
+
@@cache = {}
|
17
|
+
@@site_cache = {}
|
18
|
+
|
19
|
+
def self.find(id)
|
20
|
+
if @@cache[id].nil?
|
21
|
+
next_id = nil
|
22
|
+
xml = ""
|
23
|
+
DB.connect.exec("select next_id, xml from announcement_channel " +
|
24
|
+
"where channel_id = :id", id) do |row|
|
25
|
+
nextid = row[0].to_i
|
26
|
+
REXML::Document.new(row[1].read).write(xml, 2)
|
27
|
+
end
|
28
|
+
if nextid.nil?
|
29
|
+
raise ObjectNotFoundException.new(AnnouncementChannel, id)
|
30
|
+
end
|
31
|
+
new_announcement = AnnouncementChannel.new(id, nextid, xml)
|
32
|
+
@@cache[id] = new_announcement
|
33
|
+
@@site_cache[new_announcement.site_id] = new_announcement
|
34
|
+
end
|
35
|
+
@@cache[id]
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.find_by_site_id(site_id)
|
39
|
+
@@site_cache[site_id] ||=
|
40
|
+
if site_id == "!site"
|
41
|
+
self.find("/announcement/channel/#{site_id}/motd")
|
42
|
+
else
|
43
|
+
self.find("/announcement/channel/#{site_id}/main")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# raw data constructor
|
48
|
+
def initialize(id, nextid, xml)
|
49
|
+
@id = id
|
50
|
+
@next = nextid
|
51
|
+
@xml = xml
|
52
|
+
parse_xml
|
53
|
+
end
|
54
|
+
|
55
|
+
# properties
|
56
|
+
def announcements
|
57
|
+
@announcements ||= Announcement.find_by_channel_id(@id)
|
58
|
+
end
|
59
|
+
|
60
|
+
def announcement_count
|
61
|
+
@announcement_count ||= self.announcements.length
|
62
|
+
end
|
63
|
+
|
64
|
+
def site_id
|
65
|
+
@site_id ||= @id.split("/")[3]
|
66
|
+
end
|
67
|
+
|
68
|
+
# serialization
|
69
|
+
def default_serialization
|
70
|
+
{
|
71
|
+
"id" => self.id,
|
72
|
+
"next" => self.next,
|
73
|
+
"site_id" => self.site_id,
|
74
|
+
"announcement_count" => self.announcement_count
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def summary_serialization
|
79
|
+
{
|
80
|
+
"id" => self.id,
|
81
|
+
"site_id" => self.site_id,
|
82
|
+
"announcement_count" => self.announcement_count
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Announcement < SakaiXMLEntity
|
88
|
+
attr_reader :channel, :owner, :date
|
89
|
+
attr_reader :draft, :pubview
|
90
|
+
|
91
|
+
@@cache = {}
|
92
|
+
def self.find(id)
|
93
|
+
if @@cache[id].nil?
|
94
|
+
channel = draft = pubview = owner = date = nil
|
95
|
+
xml = ""
|
96
|
+
DB.connect.exec("select channel_id, draft, pubview, owner, " +
|
97
|
+
"to_char(message_date,'YYYY-MM-DD HH24:MI:SS'), xml " +
|
98
|
+
"from announcement_message " +
|
99
|
+
"where message_id = :id", id) do |row|
|
100
|
+
channel = AnnouncementChannel.find(row[0])
|
101
|
+
draft = row[1]
|
102
|
+
pubview = row[2]
|
103
|
+
owner = User.find(row[3])
|
104
|
+
date = row[4]
|
105
|
+
REXML::Document.new(row[5].read).write(xml, 2)
|
106
|
+
end
|
107
|
+
if date.nil?
|
108
|
+
raise ObjectNotFoundException.new(Announcement, id)
|
109
|
+
end
|
110
|
+
@@cache[id] = Announcement.new(id, channel, draft, pubview, owner, date, xml)
|
111
|
+
end
|
112
|
+
@@cache[id]
|
113
|
+
end
|
114
|
+
|
115
|
+
# raw data constructor
|
116
|
+
def initialize(id, channel, draft, pubview, owner, date, xml)
|
117
|
+
@id = id
|
118
|
+
@channel = channel
|
119
|
+
@draft = draft
|
120
|
+
@pubview = pubview
|
121
|
+
@owner = owner
|
122
|
+
@date = date
|
123
|
+
@xml = xml
|
124
|
+
parse_xml
|
125
|
+
end
|
126
|
+
|
127
|
+
# helpers
|
128
|
+
def self.find_by_channel_id(channel_id)
|
129
|
+
announcements = []
|
130
|
+
channel = AnnouncementChannel.find(channel_id)
|
131
|
+
|
132
|
+
DB.connect.exec("select message_id, draft, pubview, owner, " +
|
133
|
+
"to_char(message_date,'YYYY-MM-DD HH24:MI:SS'), xml " +
|
134
|
+
"from announcement_message " +
|
135
|
+
"where channel_id = :channel_id", channel_id) do |row|
|
136
|
+
xml = ""
|
137
|
+
id = row[0]
|
138
|
+
draft = row[1]
|
139
|
+
pubview = row[2]
|
140
|
+
owner = User.find(row[3])
|
141
|
+
date = row[4]
|
142
|
+
REXML::Document.new(row[5].read).write(xml, 2)
|
143
|
+
|
144
|
+
@@cache[id] = Announcement.new(id, channel, draft, pubview, owner, date, xml)
|
145
|
+
announcements << @@cache[id]
|
146
|
+
end
|
147
|
+
announcements
|
148
|
+
end
|
149
|
+
|
150
|
+
# serialization
|
151
|
+
def default_serialization
|
152
|
+
{
|
153
|
+
"id" => self.id,
|
154
|
+
"date" => self.date,
|
155
|
+
"owner" => self.owner.serialize(:summary),
|
156
|
+
"draft" => self.draft,
|
157
|
+
"pubview" => self.pubview,
|
158
|
+
"channel" => self.channel.serialize(:summary)
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
162
|
+
def summary_serialization
|
163
|
+
{
|
164
|
+
"id" => self.id,
|
165
|
+
"date" => self.date
|
166
|
+
}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|