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 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
+