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