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 ADDED
@@ -0,0 +1,12 @@
1
+ sakai-info Change History
2
+ =========================
3
+
4
+ 0.0.0
5
+ -----
6
+
7
+ *Released 2012-02-??*
8
+
9
+ * Initial release
10
+ * Oracle-only support
11
+ * Limited CLI functionality
12
+
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
+