rtunesu 0.2.4 → 0.3.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/History.txt +0 -5
- data/README.txt +2 -2
- data/Rakefile +15 -2
- data/VERSION +1 -0
- data/lib/rtunesu/connection.rb +152 -54
- data/lib/rtunesu/document.rb +21 -26
- data/lib/rtunesu/entities/course.rb +4 -13
- data/lib/rtunesu/entities/division.rb +5 -12
- data/lib/rtunesu/entities/external_feed.rb +14 -0
- data/lib/rtunesu/entities/group.rb +5 -14
- data/lib/rtunesu/entities/permission.rb +1 -5
- data/lib/rtunesu/entities/section.rb +15 -11
- data/lib/rtunesu/entities/site.rb +6 -13
- data/lib/rtunesu/entities/template.rb +4 -0
- data/lib/rtunesu/entities/theme.rb +3 -15
- data/lib/rtunesu/entities/track.rb +12 -27
- data/lib/rtunesu/entity.rb +185 -74
- data/lib/rtunesu/log.rb +69 -0
- data/lib/rtunesu/subentities.rb +60 -0
- data/lib/rtunesu/user.rb +3 -1
- data/lib/rtunesu/version.rb +2 -2
- data/lib/rtunesu.rb +11 -6
- data/lib/show_tree.xml +7 -0
- data/spec/connection_spec.rb +6 -30
- data/spec/document_spec.rb +82 -8
- data/spec/entities/course_spec.rb +13 -40
- data/spec/entities/division_spec.rb +13 -3
- data/spec/entities/external_feed_spec.rb +21 -0
- data/spec/entities/group_spec.rb +14 -3
- data/spec/entities/permission_spec.rb +12 -3
- data/spec/entities/section_spec.rb +32 -3
- data/spec/entities/site_spec.rb +16 -3
- data/spec/entities/track_spec.rb +32 -3
- data/spec/entity_spec.rb +147 -86
- data/spec/fixtures/requests/create_course.xml +17 -0
- data/spec/fixtures/requests/create_division.xml +11 -0
- data/spec/fixtures/requests/create_group.xml +11 -0
- data/spec/fixtures/requests/create_section.xml +11 -0
- data/spec/fixtures/requests/delete_course.xml +8 -0
- data/spec/fixtures/requests/delete_group.xml +8 -0
- data/spec/fixtures/requests/delete_section.txt +8 -0
- data/spec/fixtures/requests/show_course.xml +8 -0
- data/spec/fixtures/requests/show_group.xml +8 -0
- data/spec/fixtures/requests/show_section.xml +8 -0
- data/spec/fixtures/requests/update_course.xml +10 -0
- data/spec/fixtures/requests/update_group.xml +9 -0
- data/spec/fixtures/requests/update_section.txt +10 -0
- data/spec/fixtures/responses/failure/create_course.xml +5 -0
- data/spec/fixtures/responses/failure/create_division.xml +5 -0
- data/spec/fixtures/responses/failure/create_group.xml +5 -0
- data/spec/fixtures/responses/failure/create_section.xml +5 -0
- data/spec/fixtures/responses/failure/delete_group.xml +5 -0
- data/spec/fixtures/responses/failure/show_course.xml +11 -0
- data/spec/fixtures/responses/failure/show_division.xml +11 -0
- data/spec/fixtures/responses/failure/show_group.xml +11 -0
- data/spec/fixtures/responses/failure/show_section.xml +11 -0
- data/spec/fixtures/responses/failure/show_site.xml +11 -0
- data/spec/fixtures/responses/failure/update_section.txt +5 -0
- data/spec/fixtures/responses/success/create_course.xml +5 -0
- data/spec/fixtures/responses/success/create_division.xml +5 -0
- data/spec/fixtures/responses/success/create_group.xml +5 -0
- data/spec/fixtures/responses/success/create_section.xml +5 -0
- data/spec/fixtures/responses/success/delete_course.xml +4 -0
- data/spec/fixtures/responses/success/delete_division.xml +4 -0
- data/spec/fixtures/responses/success/delete_group.xml +4 -0
- data/spec/fixtures/responses/success/delete_section.txt +4 -0
- data/spec/fixtures/responses/success/delete_section.xml +4 -0
- data/spec/fixtures/responses/success/show_course.xml +393 -0
- data/spec/fixtures/responses/success/show_division.xml +393 -0
- data/spec/fixtures/responses/success/show_group.xml +137 -0
- data/spec/fixtures/responses/success/show_section.xml +152 -0
- data/spec/fixtures/responses/success/show_site.xml +393 -0
- data/spec/fixtures/responses/success/update_course.xml +4 -0
- data/spec/fixtures/responses/success/update_division.xml +4 -0
- data/spec/fixtures/responses/success/update_group.xml +4 -0
- data/spec/fixtures/responses/success/update_section.txt +4 -0
- data/spec/fixtures/responses/success/update_section.xml +4 -0
- data/spec/fixtures/responses/success/update_site.xml +4 -0
- data/spec/spec_helper.rb +72 -7
- data/spec/token_generation_spec.rb +23 -0
- data/spec/user_spec.rb +3 -3
- metadata +81 -76
- data/Manifest.txt +0 -77
- data/config/hoe.rb +0 -76
- data/config/requirements.rb +0 -15
- data/lib/multipart.rb +0 -53
- data/spec/documents/add_spec.rb +0 -41
- data/spec/documents/delete_spec.rb +0 -30
- data/spec/documents/merge_spec.rb +0 -30
- data/spec/documents/show_tree_spec.rb +0 -16
- data/spec/fixtures/add_course.xml +0 -26
- data/spec/fixtures/add_division.xml +0 -26
- data/spec/fixtures/add_group.xml +0 -27
- data/spec/fixtures/add_permission.xml +0 -12
- data/spec/fixtures/add_section.xml +0 -34
- data/spec/fixtures/add_track.xml +0 -19
- data/spec/fixtures/delete_course.xml +0 -8
- data/spec/fixtures/delete_division.xml +0 -8
- data/spec/fixtures/delete_group.xml +0 -8
- data/spec/fixtures/delete_permission.xml +0 -9
- data/spec/fixtures/delete_section.xml +0 -8
- data/spec/fixtures/delete_track.xml +0 -7
- data/spec/fixtures/merge_course.xml +0 -38
- data/spec/fixtures/merge_division.xml +0 -47
- data/spec/fixtures/merge_group.xml +0 -29
- data/spec/fixtures/merge_permission.xml +0 -12
- data/spec/fixtures/merge_section.xml +0 -36
- data/spec/fixtures/merge_site.xml +0 -31
- data/spec/fixtures/merge_track.xml +0 -18
- data/spec/fixtures/requests/add_coures_request.xml +0 -0
- data/spec/fixtures/show_tree.xml +0 -273
- data/spec/fixtures/update_group.xml +0 -7
- data/tasks/deployment.rake +0 -34
- data/tasks/website.rake +0 -17
- data/website/index.html +0 -54
- data/website/index.txt +0 -7
- data/website/javascripts/rounded_corners_lite.inc.js +0 -285
- data/website/stylesheets/screen.css +0 -138
- data/website/template.html.erb +0 -48
data/lib/rtunesu/entity.rb
CHANGED
@@ -1,56 +1,144 @@
|
|
1
|
-
require 'hpricot'
|
2
|
-
|
3
1
|
module RTunesU
|
4
|
-
#
|
5
|
-
#
|
2
|
+
# http://deimos.apple.com/rsrc/xsd/iTunesURequest-1.1.3.xsd
|
3
|
+
# A Base class reprenseting the various entities seen in iTunes U. Subclassed into the actual entity
|
4
|
+
# classes (Course, Division, Track, etc). Entity is mostly an object oriented interface to the
|
5
|
+
# underlying XML data returned from iTunes U. Most attributes of an Entity are read by searching
|
6
|
+
# the souce XML returned from iTunes U
|
6
7
|
# == Reading and Writing Attributes
|
7
8
|
# c = Course.find(12345, rtunes_connection_object) # finds the Course in iTunes U and stores its XML data
|
8
|
-
# c.
|
9
|
-
# c.
|
10
|
-
# c.
|
9
|
+
# c.handle # finds the <Handle> element in the XML data and returns its value (in this case 12345)
|
10
|
+
# c.name # finds the <Name> element in the XML data and returns its value (e.g. 'My Example Course')
|
11
|
+
# c.name = 'Podcasting: a Revolution' # writes a hash of unsaved data that will be sent to iTunes U.
|
11
12
|
#
|
12
13
|
# == Accessing related entities
|
13
|
-
# Related Entity objects are accessed with the pluralized form of their class name.
|
14
|
-
#
|
14
|
+
# Related Entity objects are accessed with the pluralized form of their class name.
|
15
|
+
# To access a Course's related Group entities, you would use c.groups. This will return an array of
|
16
|
+
# Group objects (or an empty Array object if there are no associated Groups)
|
17
|
+
# You can set the array of associated entities by using the '=' form of the accessor and add another
|
18
|
+
# element to the end of an array of related entities with '<<'
|
15
19
|
# Examples:
|
16
20
|
# c = Course.find(12345, rtunes_connection_object) # finds the Course in iTunes U and stores its XML data
|
17
|
-
# c.
|
18
|
-
# c.
|
19
|
-
#
|
20
|
-
# c.
|
21
|
-
#
|
22
|
-
# == Notes on arbitrary XML
|
23
|
-
# Because Entity is, at heart, an object oriented wrapper for iTunes U XML data it is possible to add arbitrary (and possibly meaningless or invalidating) data that will be sent to iTunes U. You should have a solid understanding of how Entites relate in iTunes U to avoind sending bad data.
|
24
|
-
# Examples:
|
25
|
-
# c = Course.find(12345, rtunes_connection_object)
|
26
|
-
# c.Junk = 'some junk xml'
|
27
|
-
# c.save
|
28
|
-
# # c.save will generate XML that inclucdes
|
29
|
-
# # <Course>
|
30
|
-
# # <Junk>some junk xml</Junk>
|
31
|
-
# # ... some other XML data ...
|
32
|
-
# # </Course>
|
33
|
-
# # this XML may raise errors in iTunes U because it doesn't match valid iTunes U documents
|
21
|
+
# c.groups # returns an array of Group entities or an empty array of there are no Group entities
|
22
|
+
# c.groups = [Group.new(:name => 'Lectures')] # assigns the Groups related entity array to an existing
|
23
|
+
# array (overwriting any local data about Groups)
|
24
|
+
# c.groups << Group.new(:name => 'Videos') # Adds the new Group object to the end of hte Groups array
|
25
|
+
# c.groups.collect {|g| g.name} # ['Lectures', 'Videos']
|
34
26
|
class Entity
|
35
|
-
attr_accessor :connection, :attributes, :handle, :parent, :parent_handle, :saved, :source_xml
|
36
27
|
|
37
|
-
|
28
|
+
attr_accessor :connection, :parent, :parent_handle, :saved, :source_xml
|
29
|
+
attr_reader :handle
|
30
|
+
|
31
|
+
def self.attributes
|
32
|
+
@attributes ||= Set.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.get_base_connection # :nodoc:
|
36
|
+
@base_connection
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.base_connection
|
40
|
+
Entity.get_base_connection
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.set_base_connection(connection)
|
44
|
+
@base_connection = connection
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.validates!(name, values)
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.composed_of(*names)
|
52
|
+
options = names.last.is_a?(Hash) ? names.pop : {}
|
53
|
+
self.attributes.merge(names)
|
54
|
+
names.each do |name|
|
55
|
+
storage_name = options[:as] || name.to_s.camelize
|
56
|
+
|
57
|
+
define_method(name) do
|
58
|
+
value_from_edits_or_store(storage_name)
|
59
|
+
end
|
60
|
+
|
61
|
+
unless options[:readonly]
|
62
|
+
define_method(:"#{name}=") do |arg|
|
63
|
+
edits[storage_name] = arg
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.has_a(*names)
|
70
|
+
options = names.last.is_a?(Hash) ? names.pop : {}
|
71
|
+
self.attributes.merge(names)
|
72
|
+
names.each do |name|
|
73
|
+
define_method(name) do
|
74
|
+
entity_name = options[:as] || name.to_s.camelize
|
75
|
+
instance_variable_get("@#{name}") || instance_variable_set("@#{name}", RTunesU::HasAEntityCollectionProxy.new(self.source_xml./(entity_name), self, entity_name))
|
76
|
+
end
|
77
|
+
|
78
|
+
unless options[:readonly]
|
79
|
+
define_method(:"#{name}=") do |arg|
|
80
|
+
edits[options[:as] || name.to_s.camelize] = arg
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.has_n(*names)
|
87
|
+
options = names.last.is_a?(Hash) ? names.pop : {}
|
88
|
+
self.attributes.merge(names)
|
89
|
+
names.each do |name|
|
90
|
+
define_method(name) do
|
91
|
+
entity_name = options[:as] || name.to_s.chop.camelize
|
92
|
+
instance_variable_get("@#{name}") || instance_variable_set("@#{name}", RTunesU::HasNEntityCollectionProxy.new(self.source_xml./(entity_name), self, entity_name))
|
93
|
+
end
|
94
|
+
|
95
|
+
unless options[:readonly]
|
96
|
+
define_method(:"#{name}=") do |arg|
|
97
|
+
edits[options[:as] || name.to.camelize] = arg
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Creates a new Entity object with attributes based on the hash argument Some of these attributes
|
104
|
+
# are assgined to instance variables of the obect (if there is an attr_accessor for it), the rest
|
105
|
+
# will be written to a hash of edits that will be saved to iTunes U using method_missing
|
38
106
|
def initialize(attrs = {})
|
39
|
-
self.attributes = {}
|
40
107
|
attrs.each {|attribute, value| self.send("#{attribute}=", value)}
|
108
|
+
self.source_xml = Hpricot.XML('')
|
41
109
|
end
|
42
|
-
|
43
|
-
|
110
|
+
|
111
|
+
def handle
|
112
|
+
@handle ||= handle_from_source
|
113
|
+
end
|
114
|
+
|
115
|
+
def handle_from_source
|
116
|
+
return nil unless self.source_xml
|
117
|
+
if (handle_elem = self.source_xml % 'Handle')
|
118
|
+
handle_elem.innerHTML
|
119
|
+
else
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Finds a specific entity in iTunes U. To find an entity you will need to know both its type
|
125
|
+
# (Course, Group, etc) and handle. Handles uniquely identify entities in iTunes U and the entity
|
126
|
+
# type is used to search the returned XML for the specific entity you are looking for. For example,
|
44
127
|
# Course.find(123456, rtunes_connection_object)
|
45
|
-
def self.find(handle, connection)
|
46
|
-
|
47
|
-
|
128
|
+
def self.find(handle, connection = nil)
|
129
|
+
connection ||= self.base_connection
|
130
|
+
|
131
|
+
entity = self.new
|
132
|
+
entity.instance_variable_set('@handle', handle)
|
133
|
+
entity.load_from_xml(connection.upload_file(RTunesU::SHOW_TREE_FILE, handle))
|
48
134
|
entity
|
135
|
+
|
136
|
+
rescue LocationNotFound
|
137
|
+
raise EntityNotFound, "Could not find #{entity.class_name} with handle of #{handle}."
|
49
138
|
end
|
50
|
-
|
51
|
-
def load_from_xml(
|
52
|
-
self.source_xml = Hpricot.XML(
|
53
|
-
raise EntityNotFound if self.source_xml.nil?
|
139
|
+
|
140
|
+
def load_from_xml(xml_or_entity)
|
141
|
+
self.source_xml = Hpricot.XML(xml_or_entity).at("//ITunesUResponse//#{self.class_name}//Handle[text()=#{self.handle}]..")
|
54
142
|
end
|
55
143
|
|
56
144
|
# Edits stores the changes made to an entity
|
@@ -58,6 +146,10 @@ module RTunesU
|
|
58
146
|
@edits ||= {}
|
59
147
|
end
|
60
148
|
|
149
|
+
def edited?
|
150
|
+
self.edits.any?
|
151
|
+
end
|
152
|
+
|
61
153
|
# Clear the edits and restores the loaded object to its original form
|
62
154
|
def reload
|
63
155
|
self.edits.clear
|
@@ -70,32 +162,13 @@ module RTunesU
|
|
70
162
|
nil
|
71
163
|
end
|
72
164
|
|
73
|
-
def
|
74
|
-
|
75
|
-
# read an array of related items, write one attribute, write an array of related items)
|
76
|
-
case method_name.to_s.match(/(s)*(=)*$/).captures
|
77
|
-
when [nil, "="] : self.edits[method_name.to_s[0..-2]] = args
|
78
|
-
when ["s", "="] : self.edits[method_name.to_s[0..-2]] = args
|
79
|
-
when [nil, nil] : value_from_edits_or_store(method_name.to_s)
|
80
|
-
when ["s", nil] : value_from_edits_or_store(method_name.to_s, true)
|
81
|
-
end
|
165
|
+
def value_from_edits_or_store(name)
|
166
|
+
self.edits[name] || (self.source_xml % name).innerHTML
|
82
167
|
rescue NoMethodError
|
83
|
-
|
168
|
+
nil
|
84
169
|
end
|
85
170
|
|
86
|
-
|
87
|
-
if multi
|
88
|
-
begin
|
89
|
-
self.edits[name] || (self.source_xml / name.to_s.chop).collect {|el| Object.module_eval(el.name).new(:source_xml => el)}
|
90
|
-
rescue NoMethodError
|
91
|
-
self.edits[name] = []
|
92
|
-
end
|
93
|
-
else
|
94
|
-
self.edits[name] || (self.source_xml % name).innerHTML
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
|
171
|
+
|
99
172
|
# Returns the name of the object's class ignoring namespacing.
|
100
173
|
# === Use:
|
101
174
|
# course = RTunesU::Course.new
|
@@ -105,53 +178,91 @@ module RTunesU
|
|
105
178
|
self.class.to_s.split('::').last
|
106
179
|
end
|
107
180
|
|
108
|
-
|
181
|
+
def base_connection
|
182
|
+
self.class.base_connection
|
183
|
+
end
|
184
|
+
|
185
|
+
# Returns the handle of the entitiy's parent. This can either be set directly as a string or interger or
|
186
|
+
# will access the parent entity. Sometimes you know the parent_handle without the parent object
|
187
|
+
# (for example, stored locally from an earlier request). This allows you to add a new Entity to iTunes U
|
188
|
+
# without first firing a request for a parent entity (For example, if your institution places all courses inside the
|
189
|
+
# same Section, you want to add a new Section to your Site, or a new Group to a course tied to your
|
190
|
+
# institution's LMS).
|
109
191
|
def parent_handle
|
110
192
|
self.parent ? self.parent.handle : @parent_handle
|
111
193
|
end
|
112
194
|
|
195
|
+
# Converts the entities changed attributes and subentities to XML. Called by Document when building
|
196
|
+
# documents to transfer to iTunes U.
|
113
197
|
def to_xml(xml_builder = Builder::XmlMarkup.new)
|
114
198
|
xml_builder.tag!(self.class_name) {
|
115
|
-
self.edits.each
|
116
|
-
|
199
|
+
self.edits.each do |attribute,edit|
|
200
|
+
edit.is_a?(SubentityAssociationProxy) ? edit.to_xml(xml_builder) : xml_builder.tag!(attribute, edit)
|
201
|
+
end
|
117
202
|
}
|
118
203
|
end
|
119
204
|
|
120
205
|
# called when .save is called on an object that is already stored in iTunes U
|
121
|
-
def update(connection)
|
206
|
+
def update(connection = nil)
|
207
|
+
connection ||= self.base_connection
|
208
|
+
|
122
209
|
connection.process(Document::Merge.new(self).xml)
|
210
|
+
edits.clear
|
123
211
|
self
|
124
212
|
end
|
125
213
|
|
126
214
|
# called when .save is called on an object that has no Handle (i.e. does not already exist in iTunes U)
|
127
|
-
def create(connection)
|
215
|
+
def create(connection = nil)
|
216
|
+
connection ||= self.base_connection
|
217
|
+
|
128
218
|
response = Hpricot.XML(connection.process(Document::Add.new(self).xml))
|
129
|
-
raise
|
130
|
-
|
219
|
+
raise CannotSave, response.at('error').innerHTML if response.at('error')
|
220
|
+
@handle = response.at('AddedObjectHandle').innerHTML
|
221
|
+
edits.clear
|
131
222
|
self
|
132
223
|
end
|
133
224
|
|
134
|
-
# Saves the entity to iTunes U. Save takes single argument (an iTunes U connection object).
|
135
|
-
|
225
|
+
# Saves the entity to iTunes U. Save takes single argument (an iTunes U connection object).
|
226
|
+
# If the entity is unsaved this will create the entity and populate its handle attribte.
|
227
|
+
# If the entity has already been saved it will send the updated data (if any) to iTunes U.
|
228
|
+
def save(connection = nil)
|
229
|
+
connection ||= self.base_connection
|
136
230
|
saved? ? update(connection) : create(connection)
|
137
231
|
end
|
138
232
|
|
233
|
+
# Has the entity be previously saved in iTunes U
|
139
234
|
def saved?
|
140
235
|
self.handle ? true : false
|
141
236
|
end
|
142
237
|
|
143
238
|
# Deletes the entity from iTunes U. This cannot be undone.
|
144
|
-
def delete(connection)
|
239
|
+
def delete(connection = nil)
|
240
|
+
connection ||= self.base_connection
|
241
|
+
|
145
242
|
response = Hpricot.XML(connection.process(Document::Delete.new(self).xml))
|
146
243
|
raise Exception, response.at('error').innerHTML if response.at('error')
|
147
|
-
|
244
|
+
@handle = nil
|
148
245
|
self
|
149
246
|
end
|
247
|
+
|
248
|
+
def inspect
|
249
|
+
inspected = "#<#{self.class.to_s} handle:#{@handle.inspect} "
|
250
|
+
self.class.attributes.each do |attribute|
|
251
|
+
inspected << "#{attribute}:#{self.send(attribute).inspect} "
|
252
|
+
end
|
253
|
+
inspected << '>'
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
class CannotSave < StandardError
|
258
|
+
end
|
259
|
+
|
260
|
+
class ConnectionRequired < StandardError
|
150
261
|
end
|
151
262
|
|
152
|
-
class EntityNotFound <
|
263
|
+
class EntityNotFound < StandardError
|
153
264
|
end
|
154
265
|
|
155
|
-
class MissingParent <
|
266
|
+
class MissingParent < StandardError
|
156
267
|
end
|
157
268
|
end
|
data/lib/rtunesu/log.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module RTunesU
|
4
|
+
class Log
|
5
|
+
def self.parse(log_text)
|
6
|
+
self.new(log_text).parsed
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(raw)
|
10
|
+
@raw = raw
|
11
|
+
@parsed = parse(@raw)
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :raw, :parsed
|
15
|
+
|
16
|
+
module Errors
|
17
|
+
class LogEntryParseError < RuntimeError; end
|
18
|
+
class LogParseError < RuntimeError; end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def parse_line(line)
|
23
|
+
log, path = line.split('"')
|
24
|
+
l = log.split << path
|
25
|
+
entry = { :version => l[0],
|
26
|
+
:site => l[1],
|
27
|
+
:time => Time.parse("#{l[2]} #{l[3]}"),
|
28
|
+
:action => l[4],
|
29
|
+
:uuid => l[5],
|
30
|
+
:user => l[6],
|
31
|
+
:address => l[7],
|
32
|
+
:agent => l[8],
|
33
|
+
:path => l[9]
|
34
|
+
}
|
35
|
+
unless validate(entry)
|
36
|
+
raise Errors::LogEntryParseError, "Failed to validate as an ItunesU log entry:\n#{line}"
|
37
|
+
end
|
38
|
+
entry
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse(log)
|
42
|
+
if not log.kind_of? IO and File.exists? log
|
43
|
+
log = File.open(log)
|
44
|
+
elsif not log.respond_to? 'each_line'
|
45
|
+
log = StringIO.new(log.to_s)
|
46
|
+
end
|
47
|
+
begin
|
48
|
+
log.map {|line| parse_line(line)}
|
49
|
+
rescue Errors::LogEntryParseError => error
|
50
|
+
raise Errors::LogParseError, "Failed to parse <#{log.class}>:\n#{error}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def validate(log_entry)
|
55
|
+
# if log_entry[:address].nil? or log_entry[:address].split(".").size != 4
|
56
|
+
# puts log_entry[:address].inspect
|
57
|
+
# return false
|
58
|
+
# elsif log_entry[:agent].nil? or log_entry[:agent].split("/").size != 3
|
59
|
+
# puts log_entry[:agent].inspect
|
60
|
+
# return false
|
61
|
+
# elsif log_entry[:user].nil? or log_entry[:user].split("@").size != 2
|
62
|
+
# puts log_entry[:user].inspect
|
63
|
+
# return false
|
64
|
+
# end
|
65
|
+
|
66
|
+
return true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module RTunesU
|
2
|
+
class SubentityAssociationProxy
|
3
|
+
delegate :inspect, :to => :target
|
4
|
+
def initialize(source_xml, owner, name)
|
5
|
+
@source_xml = source_xml
|
6
|
+
@owner = owner
|
7
|
+
@owner.edits[name] = self
|
8
|
+
@edits = []
|
9
|
+
self.from_xml(name)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
class HasAEntityCollectionProxy < SubentityAssociationProxy
|
15
|
+
delegate :class, :to => :target
|
16
|
+
attr_reader :target
|
17
|
+
def from_xml(name)
|
18
|
+
if @source_xml
|
19
|
+
@target = "RTunesU::#{name}".constantize.new(:source_xml => @source_xml.first, :parent_handle => @owner.handle)
|
20
|
+
else
|
21
|
+
@target = "RTunesU::#{name}".constantize.new(:parent_handle => @owner.handle)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_xml(builder)
|
26
|
+
@target.to_xml(builder) if @target.edits.any?
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing(method_name, *args)
|
30
|
+
@target.send(method_name, args)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class HasNEntityCollectionProxy < SubentityAssociationProxy
|
35
|
+
delegate :[], :at, :first, :last, :size, :to => :target
|
36
|
+
attr_reader :target, :edits
|
37
|
+
|
38
|
+
def from_xml(name)
|
39
|
+
if @source_xml
|
40
|
+
@target = @source_xml.collect {|el| Object.module_eval(el.name).new(:source_xml => el, :parent_handle => @owner.handle)}
|
41
|
+
else
|
42
|
+
@target = []
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_xml(builder)
|
47
|
+
self.edits.each {|entity| entity.to_xml(builder)}
|
48
|
+
end
|
49
|
+
|
50
|
+
def <<(entity)
|
51
|
+
@target << entity
|
52
|
+
@edits << entity
|
53
|
+
end
|
54
|
+
|
55
|
+
def []=(index,entity)
|
56
|
+
@target[index] = entity
|
57
|
+
@entity[index] = entity
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/rtunesu/user.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module RTunesU
|
2
|
-
# Represents a User for iTunes U authentication. Requests to iTunes U require User
|
2
|
+
# Represents a User for iTunes U authentication. Requests to iTunes U require User
|
3
|
+
# information in a specific format. This class exists to access data in that specific format.
|
4
|
+
# Best combined with a User object from your own authentication or LMS system.
|
3
5
|
# Webservices requests to iTunes U require your institutions administrator credentials
|
4
6
|
# === User
|
5
7
|
# u = RTunesU::User.new('191912121', 'jsmith', 'John Smith', 'jsmith@exmaple.edu')
|
data/lib/rtunesu/version.rb
CHANGED
data/lib/rtunesu.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
-
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
3
|
|
4
|
-
require '
|
4
|
+
require 'hpricot'
|
5
|
+
require 'activesupport'
|
5
6
|
|
6
7
|
require 'rtunesu/version'
|
7
8
|
require 'rtunesu/connection'
|
@@ -10,16 +11,20 @@ require 'rtunesu/document'
|
|
10
11
|
require 'rtunesu/entity'
|
11
12
|
require 'rtunesu/entities/course'
|
12
13
|
require 'rtunesu/entities/division'
|
14
|
+
require 'rtunesu/entities/external_feed'
|
13
15
|
require 'rtunesu/entities/group'
|
14
16
|
require 'rtunesu/entities/permission'
|
15
17
|
require 'rtunesu/entities/section'
|
16
18
|
require 'rtunesu/entities/site'
|
17
19
|
require 'rtunesu/entities/track'
|
18
20
|
require 'rtunesu/entities/theme'
|
21
|
+
require 'rtunesu/log'
|
22
|
+
require 'rtunesu/subentities'
|
19
23
|
|
20
24
|
module RTunesU
|
21
|
-
API_URL = 'https://deimos.apple.com/WebObjects/Core.woa/API'
|
22
|
-
API_VERSION = '1.1.
|
23
|
-
BROWSE_URL = 'https://deimos.apple.com/WebObjects/Core.woa/Browse'
|
24
|
-
SHOW_TREE_URL = 'https://deimos.apple.com/WebObjects/Core.woa/API/ShowTree'
|
25
|
+
API_URL = 'https://deimos.apple.com/WebObjects/Core.woa/API'.freeze
|
26
|
+
API_VERSION = '1.1.3'.freeze
|
27
|
+
BROWSE_URL = 'https://deimos.apple.com/WebObjects/Core.woa/Browse'.freeze
|
28
|
+
SHOW_TREE_URL = 'https://deimos.apple.com/WebObjects/Core.woa/API/ShowTree'.freeze
|
29
|
+
SHOW_TREE_FILE = File.new(File.join(File.dirname(__FILE__), 'show_tree.xml'))
|
25
30
|
end
|
data/lib/show_tree.xml
ADDED
data/spec/connection_spec.rb
CHANGED
@@ -1,35 +1,16 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
2
|
include RTunesU
|
3
3
|
|
4
|
-
describe Connection do
|
5
|
-
describe 'token generation' do
|
6
|
-
before do
|
7
|
-
Time.should_receive(:now).and_return(@current_time)
|
8
|
-
@current_time.should_receive(:to_i).and_return(1214619134)
|
9
|
-
|
10
|
-
user = mock(RTunesU::User, :id => 0,
|
11
|
-
:username => 'admin',
|
12
|
-
:name => 'Admin',
|
13
|
-
:email => 'admin@example.edu',
|
14
|
-
:credentials => ['Administrator@urn:mace:itunesu.com:sites:example.edu'],
|
15
|
-
:to_credential_string => 'Administrator@urn:mace:itunesu.com:sites:example.edu',
|
16
|
-
:to_identity_string => '"Admin" <admin@example.edu> (admin) [0]')
|
17
|
-
|
18
|
-
@connection = Connection.new(:user => user, :shared_secret => 'STRINGOFTHIRTYTWOLETTERSORDIGITS')
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'generates a token' do
|
22
|
-
@connection.generate_authorization_token.should eql("credentials=Administrator%40urn%3Amace%3Aitunesu.com%3Asites%3Aexample.edu&identity=%22Admin%22+%3Cadmin%40example.edu%3E+%28admin%29+%5B0%5D&time=1214619134&signature=121a6cf76c9c5ecda41450d87e3394b9d02c570a5f76b2bd16287f860f068302")
|
23
|
-
end
|
24
|
-
end
|
4
|
+
describe Connection do
|
25
5
|
describe 'requesting an upload location for a file' do
|
26
6
|
end
|
27
7
|
|
28
8
|
describe 'API accessing' do
|
29
9
|
before do
|
30
|
-
Time.
|
31
|
-
|
32
|
-
|
10
|
+
now = Time.new
|
11
|
+
Time.stub!(:now).and_return(now)
|
12
|
+
now.stub!(:to_i).and_return(1214619134)
|
13
|
+
|
33
14
|
user = mock(RTunesU::User, :id => 0,
|
34
15
|
:username => 'admin',
|
35
16
|
:name => 'Admin',
|
@@ -41,13 +22,8 @@ describe Connection do
|
|
41
22
|
@connection = Connection.new(:user => user, :site => 'example.edu', :shared_secret => 'STRINGOFTHIRTYTWOLETTERSORDIGITS')
|
42
23
|
end
|
43
24
|
|
44
|
-
it '
|
25
|
+
it 'generates a web services url for the institution' do
|
45
26
|
@connection.webservices_url.should eql('https://deimos.apple.com/WebObjects/Core.woa/API/ProcessWebServicesDocument/example.edu?credentials=Administrator%40urn%3Amace%3Aitunesu.com%3Asites%3Aexample.edu&identity=%22Admin%22+%3Cadmin%40example.edu%3E+%28admin%29+%5B0%5D&time=1214619134&signature=121a6cf76c9c5ecda41450d87e3394b9d02c570a5f76b2bd16287f860f068302')
|
46
27
|
end
|
47
|
-
|
48
|
-
it 'can generate a url for uploading files'
|
49
|
-
it 'opens an HTTPS connection to iTunes U'
|
50
|
-
it 'send XML data'
|
51
|
-
it 'generates a url user access to a location through iTunes U'
|
52
28
|
end
|
53
29
|
end
|