rtunesu 0.2.4 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. data/.gitignore +3 -0
  2. data/History.txt +0 -5
  3. data/README.txt +2 -2
  4. data/Rakefile +15 -2
  5. data/VERSION +1 -0
  6. data/lib/rtunesu/connection.rb +152 -54
  7. data/lib/rtunesu/document.rb +21 -26
  8. data/lib/rtunesu/entities/course.rb +4 -13
  9. data/lib/rtunesu/entities/division.rb +5 -12
  10. data/lib/rtunesu/entities/external_feed.rb +14 -0
  11. data/lib/rtunesu/entities/group.rb +5 -14
  12. data/lib/rtunesu/entities/permission.rb +1 -5
  13. data/lib/rtunesu/entities/section.rb +15 -11
  14. data/lib/rtunesu/entities/site.rb +6 -13
  15. data/lib/rtunesu/entities/template.rb +4 -0
  16. data/lib/rtunesu/entities/theme.rb +3 -15
  17. data/lib/rtunesu/entities/track.rb +12 -27
  18. data/lib/rtunesu/entity.rb +185 -74
  19. data/lib/rtunesu/log.rb +69 -0
  20. data/lib/rtunesu/subentities.rb +60 -0
  21. data/lib/rtunesu/user.rb +3 -1
  22. data/lib/rtunesu/version.rb +2 -2
  23. data/lib/rtunesu.rb +11 -6
  24. data/lib/show_tree.xml +7 -0
  25. data/spec/connection_spec.rb +6 -30
  26. data/spec/document_spec.rb +82 -8
  27. data/spec/entities/course_spec.rb +13 -40
  28. data/spec/entities/division_spec.rb +13 -3
  29. data/spec/entities/external_feed_spec.rb +21 -0
  30. data/spec/entities/group_spec.rb +14 -3
  31. data/spec/entities/permission_spec.rb +12 -3
  32. data/spec/entities/section_spec.rb +32 -3
  33. data/spec/entities/site_spec.rb +16 -3
  34. data/spec/entities/track_spec.rb +32 -3
  35. data/spec/entity_spec.rb +147 -86
  36. data/spec/fixtures/requests/create_course.xml +17 -0
  37. data/spec/fixtures/requests/create_division.xml +11 -0
  38. data/spec/fixtures/requests/create_group.xml +11 -0
  39. data/spec/fixtures/requests/create_section.xml +11 -0
  40. data/spec/fixtures/requests/delete_course.xml +8 -0
  41. data/spec/fixtures/requests/delete_group.xml +8 -0
  42. data/spec/fixtures/requests/delete_section.txt +8 -0
  43. data/spec/fixtures/requests/show_course.xml +8 -0
  44. data/spec/fixtures/requests/show_group.xml +8 -0
  45. data/spec/fixtures/requests/show_section.xml +8 -0
  46. data/spec/fixtures/requests/update_course.xml +10 -0
  47. data/spec/fixtures/requests/update_group.xml +9 -0
  48. data/spec/fixtures/requests/update_section.txt +10 -0
  49. data/spec/fixtures/responses/failure/create_course.xml +5 -0
  50. data/spec/fixtures/responses/failure/create_division.xml +5 -0
  51. data/spec/fixtures/responses/failure/create_group.xml +5 -0
  52. data/spec/fixtures/responses/failure/create_section.xml +5 -0
  53. data/spec/fixtures/responses/failure/delete_group.xml +5 -0
  54. data/spec/fixtures/responses/failure/show_course.xml +11 -0
  55. data/spec/fixtures/responses/failure/show_division.xml +11 -0
  56. data/spec/fixtures/responses/failure/show_group.xml +11 -0
  57. data/spec/fixtures/responses/failure/show_section.xml +11 -0
  58. data/spec/fixtures/responses/failure/show_site.xml +11 -0
  59. data/spec/fixtures/responses/failure/update_section.txt +5 -0
  60. data/spec/fixtures/responses/success/create_course.xml +5 -0
  61. data/spec/fixtures/responses/success/create_division.xml +5 -0
  62. data/spec/fixtures/responses/success/create_group.xml +5 -0
  63. data/spec/fixtures/responses/success/create_section.xml +5 -0
  64. data/spec/fixtures/responses/success/delete_course.xml +4 -0
  65. data/spec/fixtures/responses/success/delete_division.xml +4 -0
  66. data/spec/fixtures/responses/success/delete_group.xml +4 -0
  67. data/spec/fixtures/responses/success/delete_section.txt +4 -0
  68. data/spec/fixtures/responses/success/delete_section.xml +4 -0
  69. data/spec/fixtures/responses/success/show_course.xml +393 -0
  70. data/spec/fixtures/responses/success/show_division.xml +393 -0
  71. data/spec/fixtures/responses/success/show_group.xml +137 -0
  72. data/spec/fixtures/responses/success/show_section.xml +152 -0
  73. data/spec/fixtures/responses/success/show_site.xml +393 -0
  74. data/spec/fixtures/responses/success/update_course.xml +4 -0
  75. data/spec/fixtures/responses/success/update_division.xml +4 -0
  76. data/spec/fixtures/responses/success/update_group.xml +4 -0
  77. data/spec/fixtures/responses/success/update_section.txt +4 -0
  78. data/spec/fixtures/responses/success/update_section.xml +4 -0
  79. data/spec/fixtures/responses/success/update_site.xml +4 -0
  80. data/spec/spec_helper.rb +72 -7
  81. data/spec/token_generation_spec.rb +23 -0
  82. data/spec/user_spec.rb +3 -3
  83. metadata +81 -76
  84. data/Manifest.txt +0 -77
  85. data/config/hoe.rb +0 -76
  86. data/config/requirements.rb +0 -15
  87. data/lib/multipart.rb +0 -53
  88. data/spec/documents/add_spec.rb +0 -41
  89. data/spec/documents/delete_spec.rb +0 -30
  90. data/spec/documents/merge_spec.rb +0 -30
  91. data/spec/documents/show_tree_spec.rb +0 -16
  92. data/spec/fixtures/add_course.xml +0 -26
  93. data/spec/fixtures/add_division.xml +0 -26
  94. data/spec/fixtures/add_group.xml +0 -27
  95. data/spec/fixtures/add_permission.xml +0 -12
  96. data/spec/fixtures/add_section.xml +0 -34
  97. data/spec/fixtures/add_track.xml +0 -19
  98. data/spec/fixtures/delete_course.xml +0 -8
  99. data/spec/fixtures/delete_division.xml +0 -8
  100. data/spec/fixtures/delete_group.xml +0 -8
  101. data/spec/fixtures/delete_permission.xml +0 -9
  102. data/spec/fixtures/delete_section.xml +0 -8
  103. data/spec/fixtures/delete_track.xml +0 -7
  104. data/spec/fixtures/merge_course.xml +0 -38
  105. data/spec/fixtures/merge_division.xml +0 -47
  106. data/spec/fixtures/merge_group.xml +0 -29
  107. data/spec/fixtures/merge_permission.xml +0 -12
  108. data/spec/fixtures/merge_section.xml +0 -36
  109. data/spec/fixtures/merge_site.xml +0 -31
  110. data/spec/fixtures/merge_track.xml +0 -18
  111. data/spec/fixtures/requests/add_coures_request.xml +0 -0
  112. data/spec/fixtures/show_tree.xml +0 -273
  113. data/spec/fixtures/update_group.xml +0 -7
  114. data/tasks/deployment.rake +0 -34
  115. data/tasks/website.rake +0 -17
  116. data/website/index.html +0 -54
  117. data/website/index.txt +0 -7
  118. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  119. data/website/stylesheets/screen.css +0 -138
  120. data/website/template.html.erb +0 -48
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .DS_Store
2
+ pkg/*.*
3
+ *.gemspec
data/History.txt CHANGED
@@ -1,5 +0,0 @@
1
-
2
- Pre 0.2.3 2008-09-12
3
-
4
- * 1 major enhancement:
5
- * Initial release
data/README.txt CHANGED
@@ -3,10 +3,10 @@
3
3
  == DESCRIPTION:
4
4
  RTunesU is a ruby library for accessing Apple's iTunes U Webservices to integrate your education institutions iTunes U account into ruby applications. iTunes U's Webservices interface is fairly primitive by today's standards for XML based APIs. Some known flaws of iTunes U
5
5
  * No arbitrary search
6
- * Queries for missing objects return an XML document representing the entire institution instead of returning an error
6
+ * Queries for missing objects may return an XML document representing the entire institution instead of returning an error (rdar://7253913)
7
+ * No ability to directly find Tracks (rdar://7254419)
7
8
  * Does not follow REST principles
8
9
  * Does not use HTTP status codes meaningfully
9
- * Collections not contained in an outer element
10
10
 
11
11
  == FEATURES/PROBLEMS:
12
12
  - TODO: file uploading
data/Rakefile CHANGED
@@ -1,4 +1,17 @@
1
- require 'config/requirements'
2
- require 'config/hoe' # setup Hoe + all gem configuration
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'jeweler'
4
+
5
+ Jeweler::Tasks.new do |gemspec|
6
+ gemspec.name = 'rtunesu'
7
+ gemspec.summary = "A library for using Apple's iTunes U Webservices API"
8
+ gemspec.description = "A library for using Apple's iTunes U Webservices API"
9
+ gemspec.email = 'pietrekg@umich.edu'
10
+ gemspec.authors = ['Trek Glowacki']
11
+ gemspec.add_dependency('hpricot', '>= 0.6.164')
12
+ gemspec.add_dependency('builder', '>= 2.0')
13
+ gemspec.add_dependency('ruby-hmac', '>= 0.3.1')
14
+ end
15
+ Jeweler::GemcutterTasks.new
3
16
 
4
17
  Dir['tasks/**/*.rake'].each { |rake| load rake }
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.5
@@ -4,12 +4,14 @@ require 'hmac'
4
4
  require 'hmac-sha2'
5
5
  require 'digest'
6
6
  require 'net/https'
7
- require 'net/http/post/multipart'
7
+ require 'mime/types'
8
8
  require 'uri'
9
- require 'timeout'
10
9
 
11
10
  module RTunesU
12
- # Connection is a class for opening and using a connection to the iTunes U Webservices system. To open a connection to iTunes U, you first need to create a RTunesU::User object using the administrator data you received from Apple when they created your iTunes U site.
11
+ class LocationNotFound < StandardError; end
12
+ # Connection is a class for opening and using a connection to the iTunes U Webservices system.
13
+ # To open a connection to iTunes U, you first need to create a RTunesU::User object using the
14
+ # administrator data you received from Apple when they created your iTunes U site.
13
15
  # === Creating a Connection
14
16
  # include RTunesU
15
17
  # # create a new admin user from our Institution's login information
@@ -20,26 +22,79 @@ module RTunesU
20
22
  # connection = Connection.new(:user => user, :site => 'example.edu', :shared_secret => 'STRINGOFTHIRTYLETTERSORNUMBERS')
21
23
  #
22
24
  # === Using a Connection
23
- # A Connection object is needed for operations that communicate with Apple's iTunes U Webservices. For example, calls to .save, .create, .update, and .delete on Entity objects take a Connection object as their only argument.
24
- # To communicate with the iTunes U Service, a Connection object will generate proper authentication data, hash your request, and (if neccessary) send XML data to iTunes U.
25
+ # A Connection object is needed for operations that communicate with Apple's iTunes U Webservices.
26
+ # Connection objects can be used two ways:
27
+ # First, you can set a base connection object for all entities with
28
+ # Entity.set_base_connection(connection_object)
29
+ # This Connection and its associated user will be used for all iTunes U interaction. This is useful if
30
+ # you are generally using RTunesU as a utility libray for a learning managemnet system.
31
+ # This methodology will allow you to use methods that communicate with iTunes U without having to
32
+ # specify a connection object for each request. e.g.:
33
+ # Course.find(11234567)
34
+ # group.save
35
+ # division.delete
36
+ #
37
+ # Secondly, you supply a new connection objec to be used instead of the class connection object. This is useful for
38
+ # generating login urls for users, or allowing iTunes U's included permissions system to limit what a specfic user can do
39
+ # Pass this custom connection object as the only arguemnt to method calls that interact with iTunes U and it will
40
+ # be used instead of the classes base connection object.
41
+ # For example, calls to .save, .create, .update, and .delete on Entity objects take a Connection object as
42
+ # their only argument.
43
+ #
44
+ # Course.find(11234567, custom_connection_object_with_limited_permissions)
45
+ # group.save(custom_connection_object_with_limited_permissions)
46
+ # division.delete(custom_connection_object_with_limited_permissions)
47
+ #
48
+ # To communicate with the iTunes U Service, a Connection object will generate proper authentication data,
49
+ # hash your request, and (if neccessary) send XML data to iTunes U.
25
50
  # For more inforamtion about this processs see: http://deimos.apple.com/rsrc/doc/iTunesUAdministratorsGuide/IntegratingAuthenticationandAuthorizationServices/chapter_3_section_3.html
26
- # === Scaling tips
27
- # Because the you will likely only need a single unchanging Connection object for your application you may wish to initialize a single Connection object for the admin credentials at application start time and assign it to a constant. This is especially beneficial for long running applications like web applications.
28
- class Connection
29
- TIMEOUT = 240
30
-
51
+ class Connection
31
52
  attr_accessor :user, :options
32
53
 
33
54
  def initialize(options = {})
34
55
  self.user, self.options = options[:user], options
35
56
  end
36
57
 
37
- # iTunes U requires all request to include an authorization token that includes a User's credentials, indetifiying information, and the time of the request. This data is hashed against your institution's shared secret (provider by Apple with your iTunes U account information). Because tokens are valid only for 90 seconds they are generated for each request attempt.
58
+ # Generates HTTP mulitpart/form-data body and headers.
59
+ def http_multipart_data(params) #:nodoc:
60
+ crlf = "\r\n"
61
+ body = ""
62
+ headers = {}
63
+
64
+ boundary = "x" + Time.now.to_i.to_s(16)
65
+
66
+ headers["Content-Type"] = "multipart/form-data; boundary=#{boundary}"
67
+ headers["User-Agent"] = "RTunesU/#{RTunesU::VERSION::STRING}"
68
+ params.each do |key,value|
69
+ esc_key = key.to_s
70
+ body << "--#{boundary}#{crlf}"
71
+
72
+ if value.respond_to?(:read)
73
+ mime_type = MIME::Types.type_for(value.path)[0] || MIME::Types["application/octet-stream"][0]
74
+ body << "Content-Disposition: form-data; name=\"#{esc_key}\"; filename=\"#{File.basename(value.path)}\"#{crlf}"
75
+ body << "Content-Type: #{mime_type.simplified}#{crlf*2}"
76
+ body << value.read
77
+ value.rewind
78
+ else
79
+ body << "Content-Disposition: form-data; name=\"#{esc_key}\"#{crlf*2}#{value}"
80
+ end
81
+ end
82
+
83
+ body << "#{crlf}--#{boundary}--#{crlf*2}"
84
+ headers["Content-Length"] = body.size.to_s
85
+
86
+ return [ body, headers ]
87
+ end
88
+
89
+ # iTunes U requires all request to include an authorization token that includes a User's credentials, indetifiying information,
90
+ # and the time of the request. This data is hashed against your institution's shared secret (provided by Apple
91
+ # with your iTunes U account information). Because tokens are valid only for 90 seconds they are generated for each
92
+ # request attempt.
38
93
  def generate_authorization_token
39
- # create the token that contains the necessary elements to authorize the user
40
- # using a nested array because the alphabetical order must be maintained
41
- token = [['credentials', self.user.to_credential_string,], ['identity', self.user.to_identity_string], ['time', Time.now.to_i.to_s]]
42
- encoded_parms = token.collect {|pair| pair[1] = CGI.escape(pair[1]); pair.join('=')}.join('&')
94
+ # create the token that contains the necessary elements to authorize the user
95
+ # using a nested array because the alphabetical order must be maintained
96
+ token = [['credentials', self.user.to_credential_string,], ['identity', self.user.to_identity_string], ['time', Time.now.to_i.to_s]]
97
+ encoded_parms = token.collect {|pair| pair[1] = CGI.escape(pair[1]); pair.join('=')}.join('&')
43
98
 
44
99
  digest = Digest::SHA2.new
45
100
  digest.update(encoded_parms)
@@ -50,10 +105,10 @@ module RTunesU
50
105
  # add the hashed digital signature to the end of the query parameters
51
106
  encoded_parms += "&signature=#{hmac.hexdigest}"
52
107
  end
53
-
54
- def upload_url_for_location(location)
55
- url_string = "#{API_URL}/GetUploadURL/#{self.options[:site]}.#{location.Handle}?#{self.generate_authorization_token}"
56
- puts url_string
108
+
109
+ # Sends a request to iTunes U for a valid upload location for a file.
110
+ def upload_url_for_location(location) #:nodoc:
111
+ url_string = "#{API_URL}/GetUploadURL/#{self.options[:site]}.#{location}?#{self.generate_authorization_token}&type=XMLControlFile"
57
112
  url = URI.parse(url_string)
58
113
  http = Net::HTTP.new(url.host, url.port)
59
114
  http.use_ssl = true
@@ -61,58 +116,101 @@ module RTunesU
61
116
  response = http.start {|http|
62
117
  http.request(Net::HTTP::Get.new(url.path + '?' + url.query))
63
118
  }
119
+ raise LocationNotFound if response.kind_of?(Net::HTTPNotFound)
64
120
  response.body
65
121
  end
66
122
 
67
- # The URL that receives all iTunes U webservices requests. This is different for each institution and inclues your site name provided by Apple.
123
+ # The URL that receives all iTunes U webservices requests.
124
+ # This is different for each institution and inclues your site name provided by Apple.
68
125
  def webservices_url
69
126
  "#{API_URL}/ProcessWebServicesDocument/#{options[:site]}?#{self.generate_authorization_token}"
70
127
  end
71
128
 
72
- #
129
+ # The URL users should be redirected to access the iTunes U site.
73
130
  def browse_url
74
131
  "#{BROWSE_URL}/#{options[:site]}?#{self.generate_authorization_token}"
75
132
  end
76
133
 
77
- def show_tree
134
+ def show_tree(handle = nil, xml = nil)
78
135
  url = URI.parse("#{SHOW_TREE_URL}/#{options[:site]}?#{self.generate_authorization_token}")
79
- http = Net::HTTP.new(url.host, url.port)
80
- http.use_ssl = true
81
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
82
- http.start {
83
- request = Net::HTTP::Post.new(url.to_s)
84
- response = http.request(request)
85
- response.body
86
- }
136
+ http = Net::HTTP.new(url.host, url.port)
137
+ http.use_ssl = true
138
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
139
+ http.start {
140
+ request = Net::HTTP::Post.new(url.to_s)
141
+ response = http.request(request)
142
+ response.body
143
+ }
87
144
  end
88
145
 
89
- # Sends a string of XML data to iTunes U's webservices url for processing. Returns the iTunes U response XML. Used by Entity objects to send generated XML to iTunes U.
90
- def process(xml)
91
- timeout(TIMEOUT) do
92
- url = URI.parse(webservices_url)
93
- http = Net::HTTP.new(url.host, url.port)
94
- http.use_ssl = true
95
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
96
- http.start {
97
- request = Net::HTTP::Post.new(url.to_s)
98
- request.body = xml
99
- response = http.request(request)
100
- response.body
101
- }
102
- end
146
+ # Sends a string of XML data to iTunes U's webservices url for processing. Returns the iTunes U response XML.
147
+ # Used by Entity objects to send generated XML to iTunes U.
148
+ def process(xml, options = {})
149
+ url = URI.parse(webservices_url)
150
+ http = Net::HTTP.new(url.host, url.port)
151
+ http.use_ssl = true
152
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
153
+ http.start {
154
+ request = Net::HTTP::Post.new(url.to_s)
155
+ request.body = xml
156
+ response = http.request(request)
157
+ response.body
158
+ }
159
+ end
160
+
161
+ def upload_file(file, handle)
162
+ upload_url = upload_url_for_location(handle)
163
+
164
+ url = URI.parse(upload_url)
165
+ http = Net::HTTP.new(url.host, url.port)
166
+ http.use_ssl = true
167
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
168
+ http.start {
169
+ request = Net::HTTP::Post.new(url.path)
170
+ body, headers = http_multipart_data({:file => file})
171
+ request.body = body
172
+ request.initialize_http_header(headers)
173
+ response = http.request(request)
174
+ response.body
175
+ }
103
176
  end
104
177
 
105
- # Uploads a file from the local system to iTunes U.
106
- def upload_file(file_path, itunes_location)
107
- # url = URI.parse(self.upload_url_for_location(itunes_location))
108
- # req = Net::HTTP::Post::Multipart.new(url.path, {"file" => UploadIO.new(file_path, "image/jpeg")})
109
- # res = Net::HTTP.start(url.host, url.port) do |http|
110
- # http.request(req)
111
- # end
112
-
113
- IO::popen('-') do |c|
114
- exec "curl -q -F 'file=@#{file.path}' '#{upload_url_for_location(itunes_location)}'"
178
+ # Retrieves and parses iTunesU usage logs.
179
+ # Options:
180
+ # * +:from+:: Specifies the date to retrieve logs from. Defaults to +Date.today+
181
+ # * +:to+:: (optional) Specifies the ending date of a date range: (:from)..(:to).
182
+ # * +:parse+:: Controls whether logs are parsed or returned raw.
183
+ #
184
+ # Example:
185
+ # connection.get_logs # Retrieves and parses logs from today
186
+ # connection.get_logs :from => Date.today-1 # Retrieves and parses logs from yesterday
187
+ # connection.get_logs :parse => false # Retrieves raw logs
188
+ # connection.get_logs :from => "2004-05-13", :to => "2004-06-12"
189
+ def get_logs(opt={})
190
+ opt[:from] ||= Date.today
191
+ #opt[:to] ||= Date.today - 1
192
+ opt[:parse] = opt[:parse].nil? ? true : opt[:parse]
193
+
194
+ #raise ":from date must preceed :to date." if opt[:to] && opt[:to] < opt[:from]
195
+
196
+ uri_str = "#{API_URL}/GetDailyReportLogs/#{options[:site]}?#{self.generate_authorization_token}"
197
+ uri_str << "&StartDate=#{opt[:from]}"
198
+ uri_str << "&EndDate=#{opt[:to]}" if opt[:to]
199
+ url = URI.parse uri_str
200
+
201
+ http = Net::HTTP.new(url.host, url.port)
202
+ http.use_ssl = true
203
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
204
+ resp = http.start {
205
+ request = Net::HTTP::Get.new(url.to_s)
206
+ response = http.request(request)
207
+ response.body
208
+ }
209
+ if resp =~ /No.*?log data found/
210
+ return opt[:parse] ? [] : resp
115
211
  end
212
+
213
+ return opt[:parse] ? Log.parse(resp) : resp
116
214
  end
117
215
  end
118
216
  end
@@ -1,13 +1,15 @@
1
1
  require 'builder'
2
2
 
3
3
  module RTunesU
4
- # Document is a class that generates the neccessary XML to interact with iTunes U. Documents are generated and sent when calling .save, .create, .update, and .delete on an specific Entity object. Classes in the Document:: namespace aren't intended for direct use.
4
+ # Document is a class for generating the neccessary XML to interact with iTunes U.
5
+ # Documents are generated and sent when calling .save, .create, .update, and .delete on
6
+ # specific Entity instances. Classes in the Document:: namespace aren't intended for direct use.
5
7
  # For example:
6
- # c = Course.find(12345, itunes_connection_object)
7
- # c.Name # "Exemple Course"
8
- # c.Name = 'Example Course'
9
- # c.save(itunes_connection_object) # genertes and sends a Document::Merge object with the Course data.
10
- module Document
8
+ # c = Course.find(12345)
9
+ # c.name # "exemple course"
10
+ # c.name = 'Example Course'
11
+ # c.save # genertes and sends a Document::Merge object with the Course data.
12
+ module Document # :nodoc:
11
13
  class Base
12
14
  INDENT = 2
13
15
  attr_accessor :builder, :source, :options, :xml
@@ -26,30 +28,35 @@ module RTunesU
26
28
  self
27
29
  end
28
30
 
29
- # Implemented in each of the subclasses of Document::Base. Adds the particular action you would like to take (AddFoo, MergeFoo, DeleteFoo) to the Source class.
30
- # For example, if the source Entity is of the type Track and you are creating a Document::Add the ITunesUDocument element will have a child element of <AddTrack>...</AddTrack>
31
- # tag_action is called from inside Document::Base.new and is based the initializer's builder object so that proper nesting is maintained.
32
31
  private
32
+ # Implemented in each of the subclasses of Document::Base. Adds the particular action you would
33
+ # like to take (AddFoo, MergeFoo, DeleteFoo) to the Source class.
34
+ # For example, if the source Entity is of the type Track and you are creating a
35
+ # Document::Add the ITunesUDocument element will have a child element of <AddTrack>...</AddTrack>
36
+ # tag_action is called from inside Document::Base.new and is based the initializer's builder
37
+ # object so that proper nesting is maintained.
33
38
  def tag_action(xml_builder)
34
39
  return
35
40
  end
36
41
  end
37
42
 
38
- # Creates an XML document that comforms to iTunes U's specification for adding an entity. This class is used internally by Entity classes when saving.
43
+ # Creates an XML document that comforms to iTunes U's specification for adding an entity.
44
+ # This class is used internally by Entity classes when saving.
39
45
  class Add < Base
40
46
  private
41
47
  def tag_action(xml_builder)
42
48
  raise MissingParent if source.parent_handle.nil?
43
49
  xml_builder.tag!("Add#{source.class_name}") {
44
50
  xml_builder.tag!('ParentHandle', source.parent_handle)
45
- # The existance of ParentPath is required for iTunesU documents, but can be blank
51
+ # The existance of ParentPath is required for iTunes U to validate a document, but it can be blank
46
52
  xml_builder.tag!('ParentPath', '')
47
53
  source.to_xml(xml_builder)
48
54
  }
49
55
  end
50
56
  end
51
57
 
52
- # Creates an XML document that comforms to iTunes U's specification for updating an entity. This class is used internally by Entity classes when saving.
58
+ # Creates an XML document that comforms to iTunes U's specification for updating an entity.
59
+ # This class is used internally by Entity classes when saving.
53
60
  class Merge < Base
54
61
  private
55
62
  def tag_action(xml_builder)
@@ -60,7 +67,8 @@ module RTunesU
60
67
  end
61
68
  end
62
69
 
63
- # Creates an XML document that comforms to iTunes U's specification for deleting an entity. This class is used internally by Entity classes when saving.
70
+ # Creates an XML document that comforms to iTunes U's specification for deleting an entity.
71
+ # This class is used internally by Entity classes when saving.
64
72
  class Delete < Base
65
73
  private
66
74
  def tag_action(xml_builder)
@@ -70,18 +78,5 @@ module RTunesU
70
78
  }
71
79
  end
72
80
  end
73
-
74
- # Shows the hierarchy tree for a portion of your iTunes U site. If the source object has no handle, the tree for your entire site is shown.
75
- # ShowTree.new can take an option options hash. The possible values for this hash are
76
- # :key_group. They iTunes U 'KeyGroup' that defines the amount of data returned from the ShowTree action. Possible values are 'minimal','most','maximal'.
77
- class ShowTree < Base
78
- private
79
- def tag_action(xml_builder)
80
- xml_builder.tag!('ShowTree') {
81
- xml_builder.tag!('Handle', self.source.handle) if self.source.handle
82
- xml_builder.tag!('KeyGroup', options[:key_group] || 'most')
83
- }
84
- end
85
- end
86
81
  end
87
82
  end
@@ -1,19 +1,10 @@
1
1
  module RTunesU
2
2
  # A Course in iTunes U.
3
- # == Attributes
4
- # Name
5
- # Handle
6
- # Instructor
7
- # Description
8
- # Identifier
9
- # ThemeHandle
10
- # ShortName
11
-
12
- # == Nested Entities
13
- # Permission
14
- # Group
15
3
 
4
+ # ? LinkedFolderHandle
16
5
  class Course < Entity
17
-
6
+ composed_of :name, :instructor, :description, :identifier, :theme_handle, :short_name, :allow_subscription
7
+ composed_of :aggregate_file_size, :readonly => true
8
+ has_n :permissions, :groups
18
9
  end
19
10
  end
@@ -1,16 +1,9 @@
1
1
  module RTunesU
2
- # A Division in iTunes U is a seperate nested area of your iTunes U Site. It is different than a Section which is expressed in iTunes a a seperate area on a single page.
3
- # == Attributes
4
- # Handle
5
- # Name
6
- # ShortName
7
- # AggregateFileSize (read only)
8
- # Identifier
9
- #
10
- #
11
- # == Nested Entities
12
- # Permission
13
- # Section
2
+ # A Division in iTunes U is a seperate nested area of your iTunes U Site.
3
+ # It is different than a Section which is expressed in iTunes a a seperate area on a single page.
14
4
  class Division < Entity
5
+ composed_of :name, :short_name, :identifier, :allow_subscription, :theme_handle, :description
6
+ composed_of :aggregate_file_size, :readonly => true
7
+ has_n :permissions, :sections
15
8
  end
16
9
  end
@@ -0,0 +1,14 @@
1
+ module RTunesU
2
+ class ExternalFeed < Entity
3
+ composed_of :polling_interval, :owner_email, :security_type, :signature_type, :basic_auth_username, :basic_auth_password
4
+ composed_of :url, :as => 'URL'
5
+ validates! :polling_interval, [:never, :daily]
6
+ validates! :security_type, [:none, 'Basic Authentication']
7
+
8
+ # As an entirely nested entity ExternalFeed instances don't have a parent handle. However, something 'truthy' needs
9
+ # to be presnet for proper XML document genration.
10
+ def parent_handle # :nodoc:
11
+ ""
12
+ end
13
+ end
14
+ end
@@ -1,20 +1,11 @@
1
1
  module RTunesU
2
2
  # A Group in iTunes U. A Group is expressed as a tab in the iTunes interface.
3
- # == Attributes
4
- # Name
5
- # Handle
6
- # GroupType
7
- # ShortName
8
- # AggregateFileSize
9
- # AllowSubscription
10
- #
11
- #
12
- # == Nested Entities
13
- # Permission
14
- # Track
15
- # SharedObjects
16
- # ExternalFeed
17
3
 
18
4
  class Group < Entity
5
+ composed_of :name, :group_type, :short_name, :allow_subscription, :explicit
6
+ composed_of :aggregate_file_size, :readonly => true
7
+ validates! :group_type, [:simple, :smart, :feed]
8
+ has_a :external_feed
9
+ has_n :permissions, :tracks, :shared_objects
19
10
  end
20
11
  end
@@ -1,10 +1,6 @@
1
1
  module RTunesU
2
2
  # A Permission in iTunes U.
3
- # == Attributes
4
- # Credential
5
- # Access
6
- #
7
- # == Nested Entities
8
3
  class Permission < Entity
4
+ composed_of :credential, :access
9
5
  end
10
6
  end
@@ -1,15 +1,19 @@
1
1
  module RTunesU
2
- # A Section in iTunes U. A Section is expressed in the iTunes interface as a visually seperate area on a single page. This is different than a Division which is a seperate nested area of iTunes U.
3
- # == Attributes
4
- # Handle
5
- # Name
6
- # AllowSubscription
7
- # AggregateFileSize
8
- #
9
- # == Nested Entities
10
- # Permission
11
- # Course
12
- # Division
2
+ # A Section in iTunes U. A Section is expressed in the iTunes interface as a visually
3
+ # seperate area on a single page. This is different than a Division which is a
4
+ # seperate nested area of iTunes U.
13
5
  class Section < Entity
6
+ composed_of :name
7
+ has_n :courses
8
+
9
+ # Sections have additional required attributes for updating. These attributes *must* appear
10
+ # in a specific order.
11
+ # see http://deimos.apple.com/rsrc/doc/iTunesUAdministratorsGuide/iTunesUWebServices/chapter_17_section_12.html
12
+ def to_xml(xml_builder = Builder::XmlMarkup.new)
13
+ xml_builder.tag!("SectionPath", '')
14
+ xml_builder.tag!("MergeByHandle", "false")
15
+ xml_builder.tag!("Destructive", "false")
16
+ super
17
+ end
14
18
  end
15
19
  end
@@ -1,18 +1,11 @@
1
1
  module RTunesU
2
2
  # A Site in iTunes U.
3
- # == Attributes
4
- # Name
5
- # Handle
6
- # AllowSubscription
7
- # AggregateFileSize
8
- # ThemeHandle
9
- # LoginURL
3
+ class Site < Entity
4
+ composed_of :name, :theme_handle, :allow_subscription
5
+ has_n :permissions, :sections, :templates
6
+ end
10
7
 
11
- # == Nested Entities
12
- # Permission
13
- # Theme
14
- # LinkCollectionSet
15
- # Section
16
- class Site
8
+ def delete(connection)
9
+ raise "You cannot delete your entire site"
17
10
  end
18
11
  end
@@ -0,0 +1,4 @@
1
+ module RTunesU
2
+ class Tempalte < Entity
3
+ end
4
+ end
@@ -1,20 +1,8 @@
1
1
  module RTunesU
2
2
  # A visual theme for iTunesU pages. Color values are in 6 digit hex (e.g. #ffffff)
3
- # == Attributes
4
- # Name
5
- # Handle
6
- # BackgroundColor
7
- # LineColor
8
- # LinkArrowColor
9
- # LinkBackgroundColor
10
- # LinkBackgroundColorAlpha
11
- # LinkBoxColor
12
- # LinkTextColor
13
- # LinkTitleColor
14
- # RegularTextColor
15
- # TitleTextColor
16
- # TimeFormat
17
- # DateFormat
18
3
  class Theme < Entity
4
+ composed_of :background_color, :line_color, :link_arrow_color, :link_background_color,
5
+ :link_background_color_alpha, :link_box_color, :link_text_color, :link_title_color,
6
+ :regular_text_color, :title_text_color, :time_format, :date_format
19
7
  end
20
8
  end
@@ -1,33 +1,18 @@
1
1
  module RTunesU
2
- # A Track in iTunes U.
3
- # == Attributes
4
- # Name
5
- # Handle
6
- # Kind
7
- # TrackNumber
8
- # DiscNumber
9
- # DurationMilliseconds
10
- # AlbumName
11
- # ArtistName
12
- # GenreName
13
- # DownloadURL
14
- # Comment
15
2
  class Track < Entity
16
- # Tracks can only be found from within their Course. There is currently no way to find a Track separete from its Course in iTunes U.
17
- def self.find(handle, course_handle, connection)
18
- entity = self.new(:handle => handle)
3
+ composed_of :name, :kind, :disc_number, :album_name, :arist_name, :category_code, :explicit
4
+ composed_of :duration_in_milliseconds, :readonly => true
5
+ composed_of :downloadURL, :as => 'DownloadURL'
6
+
7
+ # Tracks can only be found from within their Course.
8
+ # There is currently (v1.1.3) no way to find a Track separete from its Course in iTunes U.
9
+ def self.find(handle, course_handle, connection = nil)
10
+ connection ||= self.base_connection
11
+
12
+ entity = self.new
13
+ entity.instance_variable_set('@handle', handle)
19
14
  entity.source_xml = Course.find(course_handle, connection).source_xml.at("Handle[text()=#{entity.handle}]..")
20
15
  entity
21
16
  end
22
-
23
- # Duration in millseconds is the one attribute in plural form that is not a collection
24
- def DurationMilliseconds
25
- self.value_from_edits_or_store('DurationMilliseconds')
26
- end
27
-
28
- # Duration in millseconds is the one attribute in plural form that is not a collection
29
- def DurationMilliseconds=(duration)
30
- self.edits['DurationMilliseconds'] = duration
31
- end
32
17
  end
33
- end
18
+ end