sakai-info 0.3.5 → 0.4.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 +8 -0
- data/README.md +8 -8
- data/ROADMAP.md +1 -7
- data/bin/{sakai-info → sin} +2 -2
- data/lib/sakai-info/cli/help.rb +71 -77
- data/lib/sakai-info/cli.rb +1 -0
- data/lib/sakai-info/content.rb +361 -58
- data/lib/sakai-info/sakai_xml_entity.rb +3 -10
- data/lib/sakai-info/user.rb +5 -1
- data/lib/sakai-info/version.rb +1 -1
- data/lib/sakai-info.rb +10 -1
- metadata +7 -7
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# sakai-info #
|
2
2
|
|
3
|
-
last updated: 2012-04-
|
3
|
+
last updated: 2012-04-21
|
4
4
|
author: David Adams (daveadams@gmail.com)
|
5
5
|
github url: https://github.com/daveadams/sakai-info
|
6
6
|
|
7
|
-
*sakai-info*
|
8
|
-
|
9
|
-
any official Sakai code
|
7
|
+
The *sakai-info* gem consists of a suite of Ruby libraries which enable the
|
8
|
+
exploration of a Sakai database without the intermediation of a Java VM or
|
9
|
+
any official Sakai code, along with a command line tool, `sin`.
|
10
10
|
|
11
11
|
Because the primary goal of this tool is to assist in information gathering
|
12
12
|
and troubleshooting, no capability to change the database is included in the
|
@@ -24,7 +24,7 @@ Use `rake` to test and build the gem:
|
|
24
24
|
$ rake gem:build
|
25
25
|
|
26
26
|
The resulting gem will be saved to the working directory as
|
27
|
-
`sakai-info-0.
|
27
|
+
`sakai-info-0.4.0.gem`.
|
28
28
|
|
29
29
|
Cleanup built gems using:
|
30
30
|
|
@@ -68,10 +68,10 @@ nickname for the connection.
|
|
68
68
|
|
69
69
|
## Command Line Usage ##
|
70
70
|
|
71
|
-
After installing the gem, the `
|
72
|
-
|
71
|
+
After installing the gem, the `sin` program should be found in your PATH. For
|
72
|
+
usage details, run:
|
73
73
|
|
74
|
-
$
|
74
|
+
$ sin help
|
75
75
|
|
76
76
|
## Library Usage ##
|
77
77
|
|
data/ROADMAP.md
CHANGED
data/bin/{sakai-info → sin}
RENAMED
@@ -1,11 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
#
|
3
|
-
#
|
3
|
+
# sin
|
4
4
|
# Command line tool for exploring the Sakai database via the
|
5
5
|
# sakai-info library
|
6
6
|
#
|
7
7
|
# Created 2012-02-15 daveadams@gmail.com
|
8
|
-
# Last updated 2012-02
|
8
|
+
# Last updated 2012-04-02 daveadams@gmail.com
|
9
9
|
#
|
10
10
|
# https://github.com/daveadams/sakai-info
|
11
11
|
#
|
data/lib/sakai-info/cli/help.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# sakai-info/cli/help.rb
|
2
|
-
# -
|
2
|
+
# - sin command line help
|
3
3
|
#
|
4
4
|
# Created 2012-02-19 daveadams@gmail.com
|
5
5
|
# Last updated 2012-04-02 daveadams@gmail.com
|
@@ -14,42 +14,24 @@ module SakaiInfo
|
|
14
14
|
class Help
|
15
15
|
STRINGS = {
|
16
16
|
:default => <<EOF,
|
17
|
-
|
18
|
-
|
19
|
-
Usage: sakai-info <command> [<id>] [<options>]
|
17
|
+
sin #{VERSION}
|
18
|
+
Usage: sin <command> [<id>] [<options>]
|
20
19
|
|
21
20
|
Object commands:
|
22
|
-
user
|
23
|
-
|
24
|
-
|
25
|
-
site Site information
|
26
|
-
page Site page information
|
27
|
-
tool Page tool information
|
28
|
-
|
29
|
-
quiz Quiz aka Assessment information, pending or published
|
30
|
-
quiz-section Quiz section information, pending or published
|
31
|
-
quiz-item Quiz item information, pending or published
|
32
|
-
quiz-attempt Quiz attempt information
|
33
|
-
quiz-attempt-item
|
34
|
-
Information on attempted quiz items
|
35
|
-
quiz-attempt-item-attachment
|
36
|
-
Information on file attachments to attempted quiz items
|
37
|
-
|
38
|
-
question-pool Question Pool information
|
39
|
-
|
40
|
-
assignment Assignment information
|
41
|
-
assignment-submission
|
42
|
-
Assignment submission information
|
43
|
-
|
44
|
-
forum Forum information
|
45
|
-
forum-thread Forum Thread information
|
46
|
-
forum-post Forum Post information
|
21
|
+
user, group, site, page, tool, quiz, quiz-section, quiz-item, quiz-attempt,
|
22
|
+
quiz-attempt-item, quiz-attempt-item-attachment, question-pool, assignment,
|
23
|
+
assignment-submission, forum, forum-thread, forum-post, content
|
47
24
|
|
48
25
|
Misc commands:
|
49
|
-
test
|
50
|
-
help
|
51
|
-
version
|
26
|
+
test Tests configured database connections
|
27
|
+
help Prints general help
|
28
|
+
version Prints version
|
29
|
+
help <command> Prints help about a particular command
|
30
|
+
help options Prints help about additional options
|
31
|
+
EOF
|
52
32
|
|
33
|
+
"options" => <<EOF,
|
34
|
+
sin #{VERSION}
|
53
35
|
Options that apply globally:
|
54
36
|
--database=<name>
|
55
37
|
Connect to database instance <name> as defined in ~/.sakai-info instead
|
@@ -78,40 +60,38 @@ sakai-info #{VERSION}
|
|
78
60
|
|
79
61
|
--all
|
80
62
|
Print all possible information (other than dbrow)
|
81
|
-
|
82
|
-
Type 'sakai-info help <command>' for help on a specific command.
|
83
63
|
EOF
|
84
64
|
|
85
65
|
"help" => <<EOF,
|
86
|
-
|
66
|
+
sin help
|
87
67
|
|
88
|
-
Usage:
|
68
|
+
Usage: sin help [<command>]
|
89
69
|
|
90
|
-
Prints usage information for other
|
70
|
+
Prints usage information for other sin commands, or without an
|
91
71
|
argument it prints a list of possible commands.
|
92
72
|
EOF
|
93
73
|
|
94
74
|
"version" => <<EOF,
|
95
|
-
|
75
|
+
sin version
|
96
76
|
|
97
|
-
Usage:
|
77
|
+
Usage: sin version
|
98
78
|
|
99
|
-
Prints the current version of
|
79
|
+
Prints the current version of sin.
|
100
80
|
EOF
|
101
81
|
|
102
82
|
"test" => <<EOF,
|
103
|
-
|
83
|
+
sin test
|
104
84
|
|
105
|
-
Usage:
|
85
|
+
Usage: sin test [<options>]
|
106
86
|
|
107
87
|
Reads configuration and tests connecting to each database specified, or if
|
108
88
|
a specific database is specified it will test only that connection.
|
109
89
|
EOF
|
110
90
|
|
111
91
|
"user" => <<EOF,
|
112
|
-
|
92
|
+
sin user
|
113
93
|
|
114
|
-
Usage:
|
94
|
+
Usage: sin user <id> [<options>]
|
115
95
|
|
116
96
|
Prints information about the user ID or EID specified. Additional options
|
117
97
|
may be passed to include additional information:
|
@@ -122,9 +102,9 @@ sakai-info user
|
|
122
102
|
EOF
|
123
103
|
|
124
104
|
"group" => <<EOF,
|
125
|
-
|
105
|
+
sin group
|
126
106
|
|
127
|
-
Usage:
|
107
|
+
Usage: sin group <id> [<options>]
|
128
108
|
|
129
109
|
Prints information about the group ID or EID specified. Additional options
|
130
110
|
may be passed to include additional information:
|
@@ -135,9 +115,9 @@ sakai-info group
|
|
135
115
|
EOF
|
136
116
|
|
137
117
|
"site" => <<EOF,
|
138
|
-
|
118
|
+
sin site
|
139
119
|
|
140
|
-
Usage:
|
120
|
+
Usage: sin site <id> [<options>]
|
141
121
|
|
142
122
|
Prints information about the site ID specified. Additional options may be
|
143
123
|
passed to include additional information:
|
@@ -156,25 +136,25 @@ sakai-info site
|
|
156
136
|
EOF
|
157
137
|
|
158
138
|
"page" => <<EOF,
|
159
|
-
|
139
|
+
sin page
|
160
140
|
|
161
|
-
Usage:
|
141
|
+
Usage: sin page <id> [<options>]
|
162
142
|
|
163
143
|
Prints information about the page ID specified, including tools.
|
164
144
|
EOF
|
165
145
|
|
166
146
|
"tool" => <<EOF,
|
167
|
-
|
147
|
+
sin tool
|
168
148
|
|
169
|
-
Usage:
|
149
|
+
Usage: sin tool <id> [<options>]
|
170
150
|
|
171
151
|
Prints information about the tool ID specified.
|
172
152
|
EOF
|
173
153
|
|
174
154
|
"quiz" => <<EOF,
|
175
|
-
|
155
|
+
sin quiz
|
176
156
|
|
177
|
-
Usage:
|
157
|
+
Usage: sin quiz <id> [<options>]
|
178
158
|
|
179
159
|
Prints information about the quiz ID specified. The quiz ID may represent
|
180
160
|
a pending quiz or a published quiz. Additional options may be passed to
|
@@ -190,9 +170,9 @@ sakai-info quiz
|
|
190
170
|
EOF
|
191
171
|
|
192
172
|
"quiz-section" => <<EOF,
|
193
|
-
|
173
|
+
sin quiz-section
|
194
174
|
|
195
|
-
Usage:
|
175
|
+
Usage: sin quiz-section <id> [<options>]
|
196
176
|
|
197
177
|
Prints information about the quiz section ID specified. The ID may represent
|
198
178
|
a pending quiz section or a published quiz section. Additional options may be
|
@@ -204,9 +184,9 @@ sakai-info quiz-section
|
|
204
184
|
EOF
|
205
185
|
|
206
186
|
"quiz-item" => <<EOF,
|
207
|
-
|
187
|
+
sin quiz-item
|
208
188
|
|
209
|
-
Usage:
|
189
|
+
Usage: sin quiz-item <id> [<options>]
|
210
190
|
|
211
191
|
Prints information about the quiz item ID specified. The ID may represent
|
212
192
|
a pending quiz item or a published quiz item. Additional options may be
|
@@ -217,10 +197,10 @@ sakai-info quiz-item
|
|
217
197
|
EOF
|
218
198
|
|
219
199
|
"question-pool" => <<EOF,
|
220
|
-
|
200
|
+
sin question-pool
|
221
201
|
|
222
|
-
Usage:
|
223
|
-
|
202
|
+
Usage: sin qpool <id> [<options>]
|
203
|
+
sin question-pool <id> [<options>]
|
224
204
|
|
225
205
|
Prints information about the question pool ID specified. Additional options
|
226
206
|
may be passed to include additional information:
|
@@ -234,9 +214,9 @@ sakai-info question-pool
|
|
234
214
|
EOF
|
235
215
|
|
236
216
|
"quiz-attempt" => <<EOF,
|
237
|
-
|
217
|
+
sin quiz-attempt
|
238
218
|
|
239
|
-
Usage:
|
219
|
+
Usage: sin quiz-attempt <id> [<options>]
|
240
220
|
|
241
221
|
Prints information about the quiz attempt ID specified. Additional options
|
242
222
|
may be passed to include additional information:
|
@@ -247,9 +227,9 @@ EOF
|
|
247
227
|
|
248
228
|
|
249
229
|
"quiz-attempt-item" => <<EOF,
|
250
|
-
|
230
|
+
sin quiz-attempt-item
|
251
231
|
|
252
|
-
Usage:
|
232
|
+
Usage: sin quiz-attempt-item <id> [<options>]
|
253
233
|
|
254
234
|
Prints information about the quiz attempt item ID specified. Additional
|
255
235
|
options may be passed to include additional information:
|
@@ -259,9 +239,9 @@ sakai-info quiz-attempt-item
|
|
259
239
|
EOF
|
260
240
|
|
261
241
|
"quiz-attempt-item-attachment" => <<EOF,
|
262
|
-
|
242
|
+
sin quiz-attempt-item-attachment
|
263
243
|
|
264
|
-
Usage:
|
244
|
+
Usage: sin quiz-attempt-item-attachment <id> [<options>]
|
265
245
|
|
266
246
|
Prints information about the quiz attempt item attachment ID specified.
|
267
247
|
Additional options may be passed to include additional information:
|
@@ -271,9 +251,9 @@ sakai-info quiz-attempt-item-attachment
|
|
271
251
|
EOF
|
272
252
|
|
273
253
|
"assignment" => <<EOF,
|
274
|
-
|
254
|
+
sin assignment
|
275
255
|
|
276
|
-
Usage:
|
256
|
+
Usage: sin assignment <id> [<options>]
|
277
257
|
|
278
258
|
Prints information about the assignment ID specified. Additional options
|
279
259
|
may be passed to include additional information:
|
@@ -285,9 +265,9 @@ sakai-info assignment
|
|
285
265
|
EOF
|
286
266
|
|
287
267
|
"assignment-submission" => <<EOF,
|
288
|
-
|
268
|
+
sin assignment-submission
|
289
269
|
|
290
|
-
Usage:
|
270
|
+
Usage: sin assignment-submission <id> [<options>]
|
291
271
|
|
292
272
|
Prints information about the assignment submission ID specified. Additional
|
293
273
|
options may be passed to include additional information:
|
@@ -298,9 +278,9 @@ sakai-info assignment-submission
|
|
298
278
|
EOF
|
299
279
|
|
300
280
|
"forum" => <<EOF,
|
301
|
-
|
281
|
+
sin forum
|
302
282
|
|
303
|
-
Usage:
|
283
|
+
Usage: sin forum <id> [<options>]
|
304
284
|
|
305
285
|
Prints information about the forum ID specified. Additional options may be
|
306
286
|
passed to include additional information:
|
@@ -311,9 +291,9 @@ sakai-info forum
|
|
311
291
|
EOF
|
312
292
|
|
313
293
|
"forum-thread" => <<EOF,
|
314
|
-
|
294
|
+
sin forum-thread
|
315
295
|
|
316
|
-
Usage:
|
296
|
+
Usage: sin forum-thread <id> [<options>]
|
317
297
|
|
318
298
|
Prints information about the forum thread ID specified. Additional options
|
319
299
|
may be passed to include additional information:
|
@@ -324,13 +304,27 @@ sakai-info forum-thread
|
|
324
304
|
EOF
|
325
305
|
|
326
306
|
"forum-post" => <<EOF,
|
327
|
-
|
307
|
+
sin forum-post
|
328
308
|
|
329
|
-
Usage:
|
309
|
+
Usage: sin forum-post <id> [<options>]
|
330
310
|
|
331
311
|
Prints information about the forum post ID specified. Additional options
|
332
312
|
may be passed to include additional information:
|
333
313
|
|
314
|
+
--mod Print creation/modification info
|
315
|
+
--all Print all possible details
|
316
|
+
EOF
|
317
|
+
|
318
|
+
"content" => <<EOF,
|
319
|
+
sin content
|
320
|
+
|
321
|
+
Usage: sin content <id> [<options>]
|
322
|
+
|
323
|
+
Prints information about the content resource or collection ID specified.
|
324
|
+
Additional options may be passed to include additional information:
|
325
|
+
|
326
|
+
--properties Print all properties
|
327
|
+
--children Recursively print collection children
|
334
328
|
--mod Print creation/modification info
|
335
329
|
--all Print all possible details
|
336
330
|
EOF
|
data/lib/sakai-info/cli.rb
CHANGED
data/lib/sakai-info/content.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# SakaiInfo::Content library
|
3
3
|
#
|
4
4
|
# Created 2012-02-17 daveadams@gmail.com
|
5
|
-
# Last updated 2012-
|
5
|
+
# Last updated 2012-04-21 daveadams@gmail.com
|
6
6
|
#
|
7
7
|
# https://github.com/daveadams/sakai-info
|
8
8
|
#
|
@@ -13,6 +13,12 @@ module SakaiInfo
|
|
13
13
|
class Content < SakaiObject
|
14
14
|
attr_reader :parent_id
|
15
15
|
|
16
|
+
include ModProps
|
17
|
+
created_by_key :_binary_entity_created_by
|
18
|
+
created_at_key :_binary_entity_created_at
|
19
|
+
modified_by_key :_binary_entity_modified_by
|
20
|
+
modified_at_key :_binary_entity_modified_at
|
21
|
+
|
16
22
|
def self.find(id)
|
17
23
|
begin
|
18
24
|
ContentResource.find(id)
|
@@ -37,18 +43,25 @@ module SakaiInfo
|
|
37
43
|
end
|
38
44
|
end
|
39
45
|
|
40
|
-
def binary
|
41
|
-
if @binary.nil?
|
42
|
-
row = DB.connect[@table_name.to_sym].filter(@id_column.to_sym => @id).first
|
43
|
-
@binary = row[:binary_entity].read
|
44
|
-
end
|
45
|
-
@binary
|
46
|
-
end
|
47
|
-
|
48
46
|
def size_on_disk
|
49
47
|
0
|
50
48
|
end
|
51
49
|
|
50
|
+
def binary_entity
|
51
|
+
if @binary_entity.nil?
|
52
|
+
@binary_entity = ContentBinaryEntity.new(@dbrow[:binary_entity])
|
53
|
+
|
54
|
+
@dbrow[:_binary_entity_created_by] = @binary_entity["CHEF:creator"]
|
55
|
+
@dbrow[:_binary_entity_modified_by] = @binary_entity["CHEF:modifiedby"]
|
56
|
+
@dbrow[:_binary_entity_created_at] =
|
57
|
+
Util.format_entity_date(@binary_entity["DAV:creationdate"])
|
58
|
+
@dbrow[:_binary_entity_modified_at] =
|
59
|
+
Util.format_entity_date(@binary_entity["DAV:getlastmodified"])
|
60
|
+
end
|
61
|
+
|
62
|
+
@binary_entity
|
63
|
+
end
|
64
|
+
|
52
65
|
def default_serialization
|
53
66
|
{
|
54
67
|
"id" => self.id,
|
@@ -71,7 +84,7 @@ module SakaiInfo
|
|
71
84
|
else
|
72
85
|
begin
|
73
86
|
@realm ||= AuthzRealm.find_by_name("/content#{@id}")
|
74
|
-
rescue
|
87
|
+
rescue ObjectNotFoundException
|
75
88
|
@realm_is_nil = true
|
76
89
|
nil
|
77
90
|
end
|
@@ -81,19 +94,67 @@ module SakaiInfo
|
|
81
94
|
def effective_realm
|
82
95
|
self.realm || (parent.nil? ? nil : parent.effective_realm)
|
83
96
|
end
|
97
|
+
|
98
|
+
def realm_serialization
|
99
|
+
if self.effective_realm.nil?
|
100
|
+
{}
|
101
|
+
else
|
102
|
+
{
|
103
|
+
"effective_realm" => self.effective_realm.name,
|
104
|
+
}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def child_summary_serialization
|
109
|
+
{
|
110
|
+
"id" => self.id.gsub(/^#{self.parent_id}/,""),
|
111
|
+
"size" => self.size_on_disk,
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def properties_serialization
|
116
|
+
result = self.binary_entity.to_hash
|
117
|
+
if result["groups"] == []
|
118
|
+
result.delete("groups")
|
119
|
+
end
|
120
|
+
result.keys.each do |key|
|
121
|
+
if result[key].nil? or result[key] == ""
|
122
|
+
result.delete(key)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
{
|
126
|
+
"properties" => result
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
# ensure @binary_entity has been parsed before returning mod info
|
131
|
+
alias :original_mod_serialization :mod_serialization
|
132
|
+
alias :original_mod_details_serialization :mod_details_serialization
|
133
|
+
|
134
|
+
def mod_serialization
|
135
|
+
self.binary_entity
|
136
|
+
self.original_mod_serialization
|
137
|
+
end
|
138
|
+
|
139
|
+
def mod_details_serialization
|
140
|
+
self.binary_entity
|
141
|
+
self.original_mod_details_serialization
|
142
|
+
end
|
84
143
|
end
|
85
144
|
|
86
145
|
class ContentResource < Content
|
87
|
-
attr_reader :file_path, :uuid, :context, :resource_type_id
|
146
|
+
attr_reader :file_path, :uuid, :context, :resource_type_id, :dbrow
|
88
147
|
|
89
|
-
def initialize(
|
90
|
-
@
|
91
|
-
|
92
|
-
@
|
93
|
-
@
|
94
|
-
@
|
95
|
-
@
|
96
|
-
@
|
148
|
+
def initialize(dbrow)
|
149
|
+
@dbrow = dbrow
|
150
|
+
|
151
|
+
@id = @dbrow[:resource_id]
|
152
|
+
@parent_id = @dbrow[:in_collection]
|
153
|
+
@file_path = @dbrow[:file_path]
|
154
|
+
@uuid = @dbrow[:resource_uuid]
|
155
|
+
@file_size = @dbrow[:file_size].to_i
|
156
|
+
@context = @dbrow[:context]
|
157
|
+
@resource_type_id = @dbrow[:resource_type_id]
|
97
158
|
|
98
159
|
@table_name = "content_resource"
|
99
160
|
@id_column = "resource_id"
|
@@ -102,11 +163,11 @@ module SakaiInfo
|
|
102
163
|
@@cache = {}
|
103
164
|
def self.find(id)
|
104
165
|
if @@cache[id].nil?
|
105
|
-
row = DB.connect[:content_resource].
|
166
|
+
row = DB.connect[:content_resource].where(:resource_id => id).first
|
106
167
|
if row.nil?
|
107
168
|
raise ObjectNotFoundException.new(ContentResource, id)
|
108
169
|
end
|
109
|
-
@@cache[id] = ContentResource.new(row
|
170
|
+
@@cache[id] = ContentResource.new(row)
|
110
171
|
end
|
111
172
|
@@cache[id]
|
112
173
|
end
|
@@ -115,40 +176,54 @@ module SakaiInfo
|
|
115
176
|
@file_size
|
116
177
|
end
|
117
178
|
|
179
|
+
def self.query_by_parent(parent_id)
|
180
|
+
DB.connect[:content_resource].where(:in_collection => parent_id)
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.find_by_parent(parent_id)
|
184
|
+
resources = []
|
185
|
+
ContentResource.query_by_parent(parent_id).all.each do |row|
|
186
|
+
@@cache[row[:resource_id]] = ContentResource.new(row)
|
187
|
+
resources << @@cache[row[:resource_id]]
|
188
|
+
end
|
189
|
+
resources
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.count_by_parent(parent_id)
|
193
|
+
ContentResource.query_by_parent(parent_id).count
|
194
|
+
end
|
195
|
+
|
118
196
|
def default_serialization
|
119
|
-
{
|
197
|
+
result = {
|
120
198
|
"id" => self.id,
|
121
|
-
"parent" => self.parent_id,
|
122
199
|
"uuid" => self.uuid,
|
123
200
|
"file_path" => self.file_path,
|
124
201
|
"size_on_disk" => self.size_on_disk,
|
125
202
|
"context" => self.context,
|
126
|
-
"
|
127
|
-
"effective_realm" => (self.effective_realm.nil? ? nil : self.effective_realm.name)
|
203
|
+
"resource_type" => self.resource_type_id,
|
128
204
|
}
|
129
|
-
|
130
|
-
|
131
|
-
def self.find_by_parent(parent_id)
|
132
|
-
resources = []
|
133
|
-
DB.connect[:content_resource].filter(:in_collection => parent_id) do |row|
|
134
|
-
@@cache[row[:resource_id]] =
|
135
|
-
ContentResource.new(row[:resource_id], row[:in_collection], row[:file_path],
|
136
|
-
row[:resource_uuid], row[:file_size].to_i, row[:context],
|
137
|
-
row[:resource_type_id])
|
138
|
-
resources << @@cache[row[0]]
|
205
|
+
if result["uuid"].nil?
|
206
|
+
result.delete("uuid")
|
139
207
|
end
|
140
|
-
|
208
|
+
result
|
141
209
|
end
|
142
210
|
|
143
|
-
def
|
144
|
-
|
211
|
+
def summary_serialization
|
212
|
+
{
|
213
|
+
"id" => self.id,
|
214
|
+
"size" => self.size_on_disk,
|
215
|
+
}
|
145
216
|
end
|
146
217
|
end
|
147
218
|
|
148
219
|
class ContentCollection < Content
|
149
|
-
|
150
|
-
|
151
|
-
|
220
|
+
attr_reader :dbrow
|
221
|
+
|
222
|
+
def initialize(dbrow)
|
223
|
+
@dbrow = dbrow
|
224
|
+
|
225
|
+
@id = @dbrow[:collection_id]
|
226
|
+
@parent_id = @dbrow[:in_collection]
|
152
227
|
|
153
228
|
@table_name = "content_collection"
|
154
229
|
@id_column = "collection_id"
|
@@ -161,11 +236,11 @@ module SakaiInfo
|
|
161
236
|
end
|
162
237
|
|
163
238
|
if @@cache[id].nil?
|
164
|
-
row = DB.connect[:content_collection].
|
239
|
+
row = DB.connect[:content_collection].where(:collection_id => id).first
|
165
240
|
if row.nil?
|
166
241
|
raise ObjectNotFoundException.new(ContentCollection, id)
|
167
242
|
end
|
168
|
-
@@cache[id] = ContentCollection.new(row
|
243
|
+
@@cache[id] = ContentCollection.new(row)
|
169
244
|
end
|
170
245
|
@@cache[id]
|
171
246
|
end
|
@@ -183,17 +258,12 @@ module SakaiInfo
|
|
183
258
|
collections = []
|
184
259
|
DB.connect[:content_collection].
|
185
260
|
where(:collection_id.like('%/portfolio-interaction/')) do |row|
|
186
|
-
@@cache[row[:collection_id]] =
|
187
|
-
ContentCollection.new(row[:collection_id], row[:in_collection])
|
261
|
+
@@cache[row[:collection_id]] = ContentCollection.new(row)
|
188
262
|
collections << @@cache[row[:collection_id]]
|
189
263
|
end
|
190
264
|
collections
|
191
265
|
end
|
192
266
|
|
193
|
-
def self.count_by_parent(parent_id)
|
194
|
-
DB.connect[:content_collection].filter(:in_collection => parent_id).count
|
195
|
-
end
|
196
|
-
|
197
267
|
def size_on_disk
|
198
268
|
@size_on_disk ||=
|
199
269
|
DB.connect[:content_resource].select(:sum.sql_function(:file_size).as(:total_size)).
|
@@ -230,36 +300,47 @@ module SakaiInfo
|
|
230
300
|
def default_serialization
|
231
301
|
{
|
232
302
|
"id" => self.id,
|
233
|
-
"parent" => self.parent_id,
|
234
303
|
"size_on_disk" => self.size_on_disk,
|
235
304
|
"children" => self.child_counts,
|
236
|
-
"effective_realm" => (self.effective_realm.nil? ? nil : effective_realm.name)
|
237
305
|
}
|
238
306
|
end
|
239
307
|
|
240
308
|
def summary_serialization
|
241
309
|
{
|
242
310
|
"id" => self.id,
|
243
|
-
"parent" => self.parent_id
|
244
311
|
}
|
245
312
|
end
|
246
313
|
|
247
314
|
def children_serialization
|
248
|
-
{
|
315
|
+
result = {
|
249
316
|
"collections" => self.children["collections"].collect { |cc|
|
250
|
-
cc.serialize(:
|
317
|
+
cc.serialize(:child_summary, :children)
|
251
318
|
},
|
252
319
|
"resources" => self.children["resources"].collect { |cr|
|
253
|
-
cr.serialize(:
|
320
|
+
cr.serialize(:child_summary)
|
254
321
|
}
|
255
322
|
}
|
323
|
+
if result["collections"] == []
|
324
|
+
result.delete("collections")
|
325
|
+
end
|
326
|
+
if result["resources"] == []
|
327
|
+
result.delete("resources")
|
328
|
+
end
|
329
|
+
result
|
330
|
+
end
|
331
|
+
|
332
|
+
def self.query_by_parent(parent_id)
|
333
|
+
DB.connect[:content_collection].where(:in_collection => parent_id)
|
334
|
+
end
|
335
|
+
|
336
|
+
def self.count_by_parent(parent_id)
|
337
|
+
ContentCollection.query_by_parent(parent_id).count
|
256
338
|
end
|
257
339
|
|
258
340
|
def self.find_by_parent(parent_id)
|
259
341
|
collections = []
|
260
|
-
|
261
|
-
@@cache[row[:collection_id]] =
|
262
|
-
ContentCollection.new(row[:collection_id], row[:in_collection])
|
342
|
+
ContentCollection.query_by_parent(parent_id).all.each do |row|
|
343
|
+
@@cache[row[:collection_id]] = ContentCollection.new(row)
|
263
344
|
collections << @@cache[row[:collection_id]]
|
264
345
|
end
|
265
346
|
collections
|
@@ -276,4 +357,226 @@ module SakaiInfo
|
|
276
357
|
0
|
277
358
|
end
|
278
359
|
end
|
360
|
+
|
361
|
+
class UnknownBinaryEntityFormat < SakaiException; end
|
362
|
+
class ContentBinaryEntity
|
363
|
+
require 'stringio'
|
364
|
+
|
365
|
+
def read_tiny_int(bin)
|
366
|
+
bin.read(1).unpack("C")[0]
|
367
|
+
end
|
368
|
+
|
369
|
+
def read_int(bin)
|
370
|
+
bin.read(2).unpack("n")[0]
|
371
|
+
end
|
372
|
+
|
373
|
+
def read_long_int(bin)
|
374
|
+
bin.read(4).unpack("N")[0]
|
375
|
+
end
|
376
|
+
|
377
|
+
def read_huge_int(bin)
|
378
|
+
part1 = bin.read(4).unpack("N")[0]
|
379
|
+
part2 = bin.read(4).unpack("N")[0]
|
380
|
+
|
381
|
+
return ((part1 << 32) + part2)
|
382
|
+
end
|
383
|
+
MAX_HUGE_INT=(2**64 - 1)
|
384
|
+
|
385
|
+
def read_utf_string(bin)
|
386
|
+
byte_length = read_int(bin)
|
387
|
+
if byte_length == 0
|
388
|
+
return ""
|
389
|
+
end
|
390
|
+
bin.read(byte_length).force_encoding(Encoding::UTF_8)
|
391
|
+
end
|
392
|
+
|
393
|
+
def initialize(raw_blob)
|
394
|
+
blob = StringIO.new(raw_blob)
|
395
|
+
blob_id = blob.read(6)
|
396
|
+
|
397
|
+
serialization_type = read_long_int(blob)
|
398
|
+
if serialization_type != 1
|
399
|
+
raise UnknownBinaryEntityFormat.new("Unrecognized format version #{serialization_type}")
|
400
|
+
end
|
401
|
+
|
402
|
+
@properties = {}
|
403
|
+
|
404
|
+
if blob_id == "CHSBRE"
|
405
|
+
parse_for_content_resource(blob)
|
406
|
+
elsif blob_id == "CHSBCE"
|
407
|
+
parse_for_content_collection(blob)
|
408
|
+
else
|
409
|
+
raise UnknownBinaryEntityFormat.new("Unrecognized format ID: #{blob_id}")
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
def [](key)
|
414
|
+
@properties[key]
|
415
|
+
end
|
416
|
+
|
417
|
+
def keys
|
418
|
+
@properties.keys
|
419
|
+
end
|
420
|
+
|
421
|
+
def to_hash
|
422
|
+
@properties
|
423
|
+
end
|
424
|
+
|
425
|
+
# content resource block IDs
|
426
|
+
# yes, block 2 = 12 and block 3 = 11
|
427
|
+
# BLOCK1 = general attributes
|
428
|
+
CR_BLOCK1 = 10
|
429
|
+
# BLOCK2 = release and retract dates
|
430
|
+
CR_BLOCK2 = 12
|
431
|
+
# BLOCK3 = groups
|
432
|
+
CR_BLOCK3 = 11
|
433
|
+
# BLOCK4 = properties
|
434
|
+
CR_BLOCK4 = 13
|
435
|
+
# BLOCK5 = file properties
|
436
|
+
CR_BLOCK5 = 14
|
437
|
+
# BLOCK6 = byte[] storing file content if it's not in the filesystem
|
438
|
+
CR_BLOCK6 = 15
|
439
|
+
# BLOCK_END
|
440
|
+
CR_BLOCK_END = 2
|
441
|
+
|
442
|
+
def parse_for_content_resource(blob)
|
443
|
+
while true
|
444
|
+
block_number = read_long_int(blob)
|
445
|
+
case(block_number)
|
446
|
+
when CR_BLOCK1
|
447
|
+
parse_block1(blob)
|
448
|
+
|
449
|
+
when CR_BLOCK2
|
450
|
+
parse_block2(blob)
|
451
|
+
|
452
|
+
when CR_BLOCK3
|
453
|
+
parse_block3(blob)
|
454
|
+
|
455
|
+
when CR_BLOCK4
|
456
|
+
parse_block4(blob)
|
457
|
+
|
458
|
+
when CR_BLOCK5
|
459
|
+
@properties["content_type"] = read_utf_string(blob)
|
460
|
+
@properties["content_length"] = read_huge_int(blob)
|
461
|
+
@properties["file_path"] = read_utf_string(blob)
|
462
|
+
|
463
|
+
when CR_BLOCK6
|
464
|
+
body_size = read_long_int(blob)
|
465
|
+
if body_size > 0
|
466
|
+
STDERR.puts "WARNING: files stored in the database are not currently supported"
|
467
|
+
end
|
468
|
+
|
469
|
+
when CR_BLOCK_END
|
470
|
+
break
|
471
|
+
|
472
|
+
else
|
473
|
+
raise UnknownBinaryEntityFormat.new("Unknown Block ID: '#{block_number}'")
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
# content collection block IDs
|
479
|
+
# BLOCK1 = general attributes
|
480
|
+
CC_BLOCK1 = 10
|
481
|
+
# BLOCK2 = release and retract dates
|
482
|
+
CC_BLOCK2 = 11
|
483
|
+
# BLOCK3 = groups
|
484
|
+
CC_BLOCK3 = 12
|
485
|
+
# BLOCK4 = properties
|
486
|
+
CC_BLOCK4 = 13
|
487
|
+
# BLOCK_END
|
488
|
+
CC_BLOCK_END = 2
|
489
|
+
|
490
|
+
def parse_for_content_collection(blob)
|
491
|
+
while true
|
492
|
+
block_number = read_long_int(blob)
|
493
|
+
case(block_number)
|
494
|
+
when CC_BLOCK1
|
495
|
+
parse_block1(blob)
|
496
|
+
|
497
|
+
when CC_BLOCK2
|
498
|
+
parse_block2(blob)
|
499
|
+
|
500
|
+
when CC_BLOCK3
|
501
|
+
parse_block3(blob)
|
502
|
+
|
503
|
+
when CC_BLOCK4
|
504
|
+
parse_block4(blob)
|
505
|
+
|
506
|
+
when CC_BLOCK_END
|
507
|
+
break
|
508
|
+
|
509
|
+
else
|
510
|
+
raise UnknownBinaryEntityFormat.new("Unknown Block ID: '#{block_number}'")
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
def parse_block1(blob)
|
516
|
+
@properties["id"] = read_utf_string(blob)
|
517
|
+
@properties["resource_type"] = read_utf_string(blob)
|
518
|
+
@properties["access_mode"] = read_utf_string(blob)
|
519
|
+
@properties["is_hidden"] = (read_tiny_int(blob) != 0)
|
520
|
+
end
|
521
|
+
|
522
|
+
def parse_block2(blob)
|
523
|
+
@properties["release_date"] = read_huge_int(blob)
|
524
|
+
if @properties["release_date"] == MAX_HUGE_INT
|
525
|
+
@properties["release_date"] = nil
|
526
|
+
end
|
527
|
+
|
528
|
+
@properties["retract_date"] = read_huge_int(blob)
|
529
|
+
if @properties["retract_date"] == MAX_HUGE_INT
|
530
|
+
@properties["retract_date"] = nil
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
def parse_block3(blob)
|
535
|
+
group_count = read_long_int(blob)
|
536
|
+
@properties["groups"] = []
|
537
|
+
if group_count > 0
|
538
|
+
group_count.times do
|
539
|
+
@properties["groups"] << read_utf_string(blob)
|
540
|
+
end
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
# properties block types
|
545
|
+
PROPS_BLOCK1 = 100
|
546
|
+
PROPS_BLOCK2 = 101
|
547
|
+
PROPS_BLOCK3 = 102
|
548
|
+
|
549
|
+
def parse_block4(blob)
|
550
|
+
type = read_long_int(blob)
|
551
|
+
if type != 1
|
552
|
+
raise UnknownBinaryEntityFormat.new("Unknown properties serialization type #{type}")
|
553
|
+
end
|
554
|
+
|
555
|
+
props_block_number = read_long_int(blob)
|
556
|
+
if props_block_number == PROPS_BLOCK1
|
557
|
+
props_count = read_long_int(blob)
|
558
|
+
else
|
559
|
+
raise UnknownBinaryEntityFormat.new("Unable to parse properties block")
|
560
|
+
end
|
561
|
+
|
562
|
+
props_count.times do
|
563
|
+
props_block_number = read_long_int(blob)
|
564
|
+
case props_block_number
|
565
|
+
when PROPS_BLOCK2
|
566
|
+
key = read_utf_string(blob)
|
567
|
+
value = read_utf_string(blob)
|
568
|
+
@properties[key] = value
|
569
|
+
when PROPS_BLOCK3
|
570
|
+
key = read_utf_string(blob)
|
571
|
+
@properties[key] = []
|
572
|
+
value_count = read_long_int(blob)
|
573
|
+
value_count.times do
|
574
|
+
@properties[key] << read_utf_string(blob)
|
575
|
+
end
|
576
|
+
else
|
577
|
+
raise UnknownBinaryEntityFormat.new("Unknown Property Block ID: '#{block_number}'")
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|
279
582
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# SakaiInfo::SakaiXMLEntity
|
3
3
|
#
|
4
4
|
# Created 2012-02-16 daveadams@gmail.com
|
5
|
-
# Last updated 2012-
|
5
|
+
# Last updated 2012-04-21 daveadams@gmail.com
|
6
6
|
#
|
7
7
|
# https://github.com/daveadams/sakai-info
|
8
8
|
#
|
@@ -77,15 +77,8 @@ module SakaiInfo
|
|
77
77
|
|
78
78
|
@dbrow[:_xml_entity_created_by] = @properties["CHEF:creator"]
|
79
79
|
@dbrow[:_xml_entity_modified_by] = @properties["CHEF:modifiedby"]
|
80
|
-
@dbrow[:_xml_entity_created_at] = format_entity_date(@properties["DAV:creationdate"])
|
81
|
-
@dbrow[:_xml_entity_modified_at] = format_entity_date(@properties["DAV:getlastmodified"])
|
82
|
-
end
|
83
|
-
|
84
|
-
def format_entity_date(raw)
|
85
|
-
if raw =~ /^(....)(..)(..)(..)(..)(..).*$/
|
86
|
-
# I believe these are usually in UTC
|
87
|
-
Time.utc($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i).getlocal
|
88
|
-
end
|
80
|
+
@dbrow[:_xml_entity_created_at] = Util.format_entity_date(@properties["DAV:creationdate"])
|
81
|
+
@dbrow[:_xml_entity_modified_at] = Util.format_entity_date(@properties["DAV:getlastmodified"])
|
89
82
|
end
|
90
83
|
|
91
84
|
# serialize all attributes
|
data/lib/sakai-info/user.rb
CHANGED
@@ -159,7 +159,7 @@ module SakaiInfo
|
|
159
159
|
|
160
160
|
# yaml/json serialization
|
161
161
|
def default_serialization
|
162
|
-
{
|
162
|
+
result = {
|
163
163
|
"id" => self.id,
|
164
164
|
"name" => self.name,
|
165
165
|
"eid" => self.eid,
|
@@ -169,6 +169,10 @@ module SakaiInfo
|
|
169
169
|
"site_count" => self.site_count,
|
170
170
|
"question_pool_count" => self.question_pool_count
|
171
171
|
}
|
172
|
+
if result["user_properties"] == {}
|
173
|
+
result.delete("user_properties")
|
174
|
+
end
|
175
|
+
result
|
172
176
|
end
|
173
177
|
|
174
178
|
def summary_serialization
|
data/lib/sakai-info/version.rb
CHANGED
data/lib/sakai-info.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# Base library file
|
3
3
|
#
|
4
4
|
# Created 2012-02-15 daveadams@gmail.com
|
5
|
-
# Last updated 2012-
|
5
|
+
# Last updated 2012-04-21 daveadams@gmail.com
|
6
6
|
#
|
7
7
|
# https://github.com/daveadams/sakai-info
|
8
8
|
#
|
@@ -59,6 +59,15 @@ module SakaiInfo
|
|
59
59
|
"#{negative ? "-" : ""}#{sprintf("%.1f", size)} #{FILESIZE_LABELS[label]}"
|
60
60
|
end
|
61
61
|
end
|
62
|
+
|
63
|
+
def self.format_entity_date(raw)
|
64
|
+
if raw =~ /^(....)(..)(..)(..)(..)(..).*$/
|
65
|
+
# I believe these are usually in UTC
|
66
|
+
Time.utc($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i).getlocal
|
67
|
+
else
|
68
|
+
raw
|
69
|
+
end
|
70
|
+
end
|
62
71
|
end
|
63
72
|
end
|
64
73
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sakai-info
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- David Adams
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-04-
|
18
|
+
date: 2012-04-21 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -49,7 +49,7 @@ dependencies:
|
|
49
49
|
description: A command line tool and a suite of libraries for representing the objects and relationships defined by a Sakai CLE database.
|
50
50
|
email: daveadams@gmail.com
|
51
51
|
executables:
|
52
|
-
-
|
52
|
+
- sin
|
53
53
|
extensions: []
|
54
54
|
|
55
55
|
extra_rdoc_files: []
|
@@ -78,7 +78,7 @@ files:
|
|
78
78
|
- lib/sakai-info/content.rb
|
79
79
|
- lib/sakai-info/page.rb
|
80
80
|
- lib/sakai-info/sakai_xml_entity.rb
|
81
|
-
- bin/
|
81
|
+
- bin/sin
|
82
82
|
- README.md
|
83
83
|
- LICENSE
|
84
84
|
- CHANGELOG.md
|