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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # sakai-info Change History #
2
2
 
3
+ ### 0.4.0 ###
4
+
5
+ *Released 2012-04-21*
6
+
7
+ * CLI access to content
8
+ * Content binary entity parsing
9
+ * Command line tool name change to "sin"
10
+
3
11
  ## 0.3.5 ##
4
12
 
5
13
  *Released 2012-04-02*
data/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # sakai-info #
2
2
 
3
- last updated: 2012-04-02
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* is a command line tool and a suite of Ruby libraries which enable
8
- the exploration of a Sakai database without the intermediation of a Java VM or
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.3.5.gem`.
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 `sakai-info` program should be found in your
72
- PATH. For usage details, run:
71
+ After installing the gem, the `sin` program should be found in your PATH. For
72
+ usage details, run:
73
73
 
74
- $ sakai-info help
74
+ $ sin help
75
75
 
76
76
  ## Library Usage ##
77
77
 
data/ROADMAP.md CHANGED
@@ -1,12 +1,6 @@
1
1
  # sakai-info Roadmap #
2
2
 
3
- *Last updated 2012-04-02*
4
-
5
- ### 0.4.0 ###
6
-
7
- * CLI access to content
8
- * Content binary entity parsing
9
- * Command line tool name change to "sin"
3
+ *Last updated 2012-04-21*
10
4
 
11
5
  ### 0.4.1 ###
12
6
 
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # sakai-info
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-26 daveadams@gmail.com
8
+ # Last updated 2012-04-02 daveadams@gmail.com
9
9
  #
10
10
  # https://github.com/daveadams/sakai-info
11
11
  #
@@ -1,5 +1,5 @@
1
1
  # sakai-info/cli/help.rb
2
- # - sakai-info command line help
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
- sakai-info #{VERSION}
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 User information
23
- group Group information
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 Tests configured database connections
50
- help Prints general help
51
- version Prints 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
- sakai-info help
66
+ sin help
87
67
 
88
- Usage: sakai-info help [<command>]
68
+ Usage: sin help [<command>]
89
69
 
90
- Prints usage information for other sakai-info commands, or without an
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
- sakai-info version
75
+ sin version
96
76
 
97
- Usage: sakai-info version
77
+ Usage: sin version
98
78
 
99
- Prints the current version of sakai-info.
79
+ Prints the current version of sin.
100
80
  EOF
101
81
 
102
82
  "test" => <<EOF,
103
- sakai-info test
83
+ sin test
104
84
 
105
- Usage: sakai-info test [<options>]
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
- sakai-info user
92
+ sin user
113
93
 
114
- Usage: sakai-info user <id> [<options>]
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
- sakai-info group
105
+ sin group
126
106
 
127
- Usage: sakai-info group <id> [<options>]
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
- sakai-info site
118
+ sin site
139
119
 
140
- Usage: sakai-info site <id> [<options>]
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
- sakai-info page
139
+ sin page
160
140
 
161
- Usage: sakai-info page <id> [<options>]
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
- sakai-info tool
147
+ sin tool
168
148
 
169
- Usage: sakai-info tool <id> [<options>]
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
- sakai-info quiz
155
+ sin quiz
176
156
 
177
- Usage: sakai-info quiz <id> [<options>]
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
- sakai-info quiz-section
173
+ sin quiz-section
194
174
 
195
- Usage: sakai-info quiz-section <id> [<options>]
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
- sakai-info quiz-item
187
+ sin quiz-item
208
188
 
209
- Usage: sakai-info quiz-item <id> [<options>]
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
- sakai-info question-pool
200
+ sin question-pool
221
201
 
222
- Usage: sakai-info qpool <id> [<options>]
223
- sakai-info question-pool <id> [<options>]
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
- sakai-info quiz-attempt
217
+ sin quiz-attempt
238
218
 
239
- Usage: sakai-info quiz-attempt <id> [<options>]
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
- sakai-info quiz-attempt-item
230
+ sin quiz-attempt-item
251
231
 
252
- Usage: sakai-info quiz-attempt-item <id> [<options>]
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
- sakai-info quiz-attempt-item-attachment
242
+ sin quiz-attempt-item-attachment
263
243
 
264
- Usage: sakai-info quiz-attempt-item-attachment <id> [<options>]
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
- sakai-info assignment
254
+ sin assignment
275
255
 
276
- Usage: sakai-info assignment <id> [<options>]
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
- sakai-info assignment-submission
268
+ sin assignment-submission
289
269
 
290
- Usage: sakai-info assignment-submission <id> [<options>]
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
- sakai-info forum
281
+ sin forum
302
282
 
303
- Usage: sakai-info forum <id> [<options>]
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
- sakai-info forum-thread
294
+ sin forum-thread
315
295
 
316
- Usage: sakai-info forum-thread <id> [<options>]
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
- sakai-info forum-post
307
+ sin forum-post
328
308
 
329
- Usage: sakai-info forum-post <id> [<options>]
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
@@ -35,6 +35,7 @@ module SakaiInfo
35
35
  "forum" => Forum,
36
36
  "forum-thread" => ForumThread,
37
37
  "forum-post" => ForumPost,
38
+ "content" => Content,
38
39
  }
39
40
  end
40
41
  end
@@ -2,7 +2,7 @@
2
2
  # SakaiInfo::Content library
3
3
  #
4
4
  # Created 2012-02-17 daveadams@gmail.com
5
- # Last updated 2012-02-24 daveadams@gmail.com
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 AuthzRealmNotFoundException
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(id, parent_id, file_path, uuid, file_size, context, resource_type_id)
90
- @id = id
91
- @parent_id = parent_id
92
- @file_path = File.join(Instance.content_base_directory, file_path)
93
- @uuid = uuid
94
- @file_size = file_size
95
- @context = context
96
- @resource_type_id = resource_type_id
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].filter(:resource_id => id).first
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[:resource_id], row[:in_collection], row[:file_path], row[:resource_uuid], row[:file_size].to_i, row[:context], row[:resource_type_id])
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
- "resource_type_id" => self.resource_type_id,
127
- "effective_realm" => (self.effective_realm.nil? ? nil : self.effective_realm.name)
203
+ "resource_type" => self.resource_type_id,
128
204
  }
129
- end
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
- resources
208
+ result
141
209
  end
142
210
 
143
- def self.count_by_parent(parent_id)
144
- DB.connect[:content_resource].filter(:in_collection => parent_id).count
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
- def initialize(id, parent_id)
150
- @id = id
151
- @parent_id = parent_id
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].filter(:collection_id => id).first
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[:collection_id], row[:in_collection])
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(:summary, :children)
317
+ cc.serialize(:child_summary, :children)
251
318
  },
252
319
  "resources" => self.children["resources"].collect { |cr|
253
- cr.serialize(:summary)
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
- DB.connect[:content_collection].filter(:in_collection => parent_id) do |row|
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-02-29 daveadams@gmail.com
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module SakaiInfo
2
- VERSION = "0.3.5"
2
+ VERSION = "0.4.0"
3
3
  end
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-02-25 daveadams@gmail.com
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: 25
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 5
10
- version: 0.3.5
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-02 00:00:00 -05:00
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
- - sakai-info
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/sakai-info
81
+ - bin/sin
82
82
  - README.md
83
83
  - LICENSE
84
84
  - CHANGELOG.md