sakai-info 0.3.5 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|