sakai-info 0.1.0
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/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
|
+
|