UPnP-ContentDirectory 1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data.tar.gz.sig +4 -0
- data/History.txt +5 -0
- data/Manifest.txt +5 -0
- data/README.txt +83 -0
- data/Rakefile +14 -0
- data/lib/UPnP/service/content_directory.rb +401 -0
- metadata +101 -0
- metadata.gz.sig +4 -0
data.tar.gz.sig
ADDED
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
= UPnP-ContentDirectory
|
2
|
+
|
3
|
+
* http://seattlerb.org/UPnP-ContentDirectory
|
4
|
+
* http://upnp.org
|
5
|
+
* Bugs: http://rubyforge.org/tracker/?func=add&group_id=1513&atid=5921
|
6
|
+
|
7
|
+
== DESCRIPTION:
|
8
|
+
|
9
|
+
A UPnP ContentDirectory service with some DLNA extensions. Currently this is
|
10
|
+
a work in progress, and is only adequate for viewing images on a PlayStation
|
11
|
+
3.
|
12
|
+
|
13
|
+
== FEATURES/PROBLEMS:
|
14
|
+
|
15
|
+
* Only direct filesystem representations currently supported
|
16
|
+
* Largely undocumented
|
17
|
+
* Only tested on PlayStation 3
|
18
|
+
* Only images known to work
|
19
|
+
* Partially implemented:
|
20
|
+
* Browse
|
21
|
+
* GetSystemUpdateID
|
22
|
+
* Not implemented:
|
23
|
+
* CreateObject
|
24
|
+
* CreateReference
|
25
|
+
* DeleteResource
|
26
|
+
* DestroyObject
|
27
|
+
* ExportResource
|
28
|
+
* GetSearchCapabilities
|
29
|
+
* GetSortCapabilities
|
30
|
+
* GetTransferProgress
|
31
|
+
* ImportResource
|
32
|
+
* Search
|
33
|
+
* StopTransferResource
|
34
|
+
* UpdateObject
|
35
|
+
|
36
|
+
== SYNOPSIS:
|
37
|
+
|
38
|
+
In a UPnP::Device::create block:
|
39
|
+
|
40
|
+
your_device.add_service 'ContentDirectory' do |cd|
|
41
|
+
cd.add_directory Dir.pwd
|
42
|
+
end
|
43
|
+
|
44
|
+
See UPnP::Device::MediaServer for an example of usage.
|
45
|
+
|
46
|
+
== REQUIREMENTS:
|
47
|
+
|
48
|
+
* A filesystem with files on it
|
49
|
+
* A UPnP control point
|
50
|
+
|
51
|
+
== INSTALL:
|
52
|
+
|
53
|
+
sudo gem install UPnP-ContentDirectory
|
54
|
+
|
55
|
+
== LICENSE:
|
56
|
+
|
57
|
+
Copyright 2008 Eric Hodel. All rights reserved.
|
58
|
+
|
59
|
+
Redistribution and use in source and binary forms, with or without
|
60
|
+
modification, are permitted provided that the following conditions
|
61
|
+
are met:
|
62
|
+
|
63
|
+
1. Redistributions of source code must retain the above copyright
|
64
|
+
notice, this list of conditions and the following disclaimer.
|
65
|
+
2. Redistributions in binary form must reproduce the above copyright
|
66
|
+
notice, this list of conditions and the following disclaimer in the
|
67
|
+
documentation and/or other materials provided with the distribution.
|
68
|
+
3. Neither the names of the authors nor the names of their contributors
|
69
|
+
may be used to endorse or promote products derived from this software
|
70
|
+
without specific prior written permission.
|
71
|
+
|
72
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
|
73
|
+
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
74
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
75
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
|
76
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
77
|
+
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
78
|
+
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
79
|
+
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
80
|
+
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
81
|
+
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
82
|
+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
83
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
require './lib/UPnP/service/content_directory'
|
6
|
+
|
7
|
+
Hoe.new 'UPnP-ContentDirectory', UPnP::Service::ContentDirectory::VERSION do |p|
|
8
|
+
p.rubyforge_name = 'seattlerb'
|
9
|
+
p.developer 'Eric Hodel', 'drbrain@segment7.net'
|
10
|
+
|
11
|
+
p.extra_deps << ['UPnP', '>= 1.1.0']
|
12
|
+
end
|
13
|
+
|
14
|
+
# vim: syntax=Ruby
|
@@ -0,0 +1,401 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'UPnP/service'
|
6
|
+
|
7
|
+
##
|
8
|
+
# A UPnP ContentDirectory service. See upnp.org for specifications.
|
9
|
+
|
10
|
+
class UPnP::Service::ContentDirectory < UPnP::Service
|
11
|
+
|
12
|
+
VERSION = '1.0'
|
13
|
+
|
14
|
+
##
|
15
|
+
# Returns the searching capabilities supported by the device
|
16
|
+
|
17
|
+
add_action 'GetSearchCapabilities',
|
18
|
+
[OUT, 'SearchCaps', 'SearchCapabilities']
|
19
|
+
|
20
|
+
##
|
21
|
+
# Returns the CSV list of metadata tags that can be used in sortCriteria
|
22
|
+
|
23
|
+
add_action 'GetSortCapabilities',
|
24
|
+
[OUT, 'SortCaps', 'SortCapabilities']
|
25
|
+
|
26
|
+
add_action 'GetSystemUpdateID',
|
27
|
+
[OUT, 'Id', 'SystemUpdateID']
|
28
|
+
|
29
|
+
add_action 'Browse',
|
30
|
+
[IN, 'ObjectID', 'A_ARG_TYPE_ObjectID'],
|
31
|
+
[IN, 'BrowseFlag', 'A_ARG_TYPE_BrowseFlag'],
|
32
|
+
[IN, 'Filter', 'A_ARG_TYPE_Filter'],
|
33
|
+
[IN, 'StartingIndex', 'A_ARG_TYPE_Index'],
|
34
|
+
[IN, 'RequestedCount', 'A_ARG_TYPE_Count'],
|
35
|
+
[IN, 'SortCriteria', 'A_ARG_TYPE_SortCriteria'],
|
36
|
+
|
37
|
+
[OUT, 'Result', 'A_ARG_TYPE_Result'],
|
38
|
+
[OUT, 'NumberReturned', 'A_ARG_TYPE_Count'],
|
39
|
+
[OUT, 'TotalMatches', 'A_ARG_TYPE_Count'],
|
40
|
+
[OUT, 'UpdateID', 'A_ARG_TYPE_UpdateID']
|
41
|
+
|
42
|
+
# optional actions
|
43
|
+
|
44
|
+
add_action 'Search',
|
45
|
+
[IN, 'ContainerID', 'A_ARG_TYPE_ObjectID'],
|
46
|
+
[IN, 'SearchCriteria', 'A_ARG_TYPE_SearchCriteria'],
|
47
|
+
[IN, 'Filter', 'A_ARG_TYPE_Filter'],
|
48
|
+
[IN, 'StartingIndex', 'A_ARG_TYPE_Index'],
|
49
|
+
[IN, 'RequestedCount', 'A_ARG_TYPE_Count'],
|
50
|
+
[IN, 'SortCriteria', 'A_ARG_TYPE_SortCriteria'],
|
51
|
+
|
52
|
+
[OUT, 'Result', 'A_ARG_TYPE_Result'],
|
53
|
+
[OUT, 'NumberReturned', 'A_ARG_TYPE_Count'],
|
54
|
+
[OUT, 'TotalMatches', 'A_ARG_TYPE_Count'],
|
55
|
+
[OUT, 'UpdateID', 'A_ARG_TYPE_UpdateID']
|
56
|
+
|
57
|
+
add_action 'CreateObject',
|
58
|
+
[IN, 'ContainerID', 'A_ARG_TYPE_ObjectID'],
|
59
|
+
[IN, 'Elements', 'A_ARG_TYPE_Result'],
|
60
|
+
|
61
|
+
[OUT, 'ObjectID', 'A_ARG_TYPE_ObjectID'],
|
62
|
+
[OUT, 'Result', 'A_ARG_TYPE_Result']
|
63
|
+
|
64
|
+
add_action 'DestroyObject',
|
65
|
+
[IN, 'ObjectID', 'A_ARG_TYPE_ObjectID']
|
66
|
+
|
67
|
+
add_action 'UpdateObject',
|
68
|
+
[IN, 'ObjectID', 'A_ARG_TYPE_ObjectID'],
|
69
|
+
[IN, 'CurrentTagValue', 'A_ARG_TYPE_TagValueList'],
|
70
|
+
[IN, 'NewTagValue', 'A_ARG_TYPE_TagValueList']
|
71
|
+
|
72
|
+
add_action 'ImportResource',
|
73
|
+
[IN, 'SourceURI', 'A_ARG_TYPE_URI'],
|
74
|
+
[IN, 'DestinationURI', 'A_ARG_TYPE_URI'],
|
75
|
+
|
76
|
+
[OUT, 'TransferID', 'A_ARG_TYPE_TransferID']
|
77
|
+
|
78
|
+
add_action 'ExportResource',
|
79
|
+
[IN, 'SourceURI', 'A_ARG_TYPE_URI'],
|
80
|
+
[IN, 'DestinationURI', 'A_ARG_TYPE_URI'],
|
81
|
+
|
82
|
+
[OUT, 'TransferID', 'A_ARG_TYPE_TransferID']
|
83
|
+
|
84
|
+
add_action 'StopTransferResource',
|
85
|
+
[IN, 'TransferID', 'A_ARG_TYPE_TransferID']
|
86
|
+
|
87
|
+
add_action 'GetTransferProgress',
|
88
|
+
[IN, 'TransferID', 'A_ARG_TYPE_TransferID'],
|
89
|
+
|
90
|
+
[OUT, 'TransferStatus', 'A_ARG_TYPE_TransferStatus'],
|
91
|
+
[OUT, 'TransferLength', 'A_ARG_TYPE_TransferLength'],
|
92
|
+
[OUT, 'TransferTotal', 'A_ARG_TYPE_TransferTotal']
|
93
|
+
|
94
|
+
add_action 'DeleteResource',
|
95
|
+
[IN, 'ResourceURI', 'A_ARG_TYPE_URI']
|
96
|
+
|
97
|
+
add_action 'CreateReference',
|
98
|
+
[IN, 'ContainerID', 'A_ARG_TYPE_ObjectID'],
|
99
|
+
[IN, 'ObjectID', 'A_ARG_TYPE_ObjectID'],
|
100
|
+
[OUT, 'NewID', 'A_ARG_TYPE_ObjectID']
|
101
|
+
|
102
|
+
add_variable 'TransferIDs', 'string', nil, nil, true
|
103
|
+
add_variable 'A_ARG_TYPE_ObjectID', 'string'
|
104
|
+
add_variable 'A_ARG_TYPE_Result', 'string' # 2.5.4 - DIDL-Lite
|
105
|
+
add_variable 'A_ARG_TYPE_SearchCriteria', 'string' # 2.5.5
|
106
|
+
add_variable 'A_ARG_TYPE_BrowseFlag', 'string',
|
107
|
+
%w[BrowseMetadata BrowseDirectChildren]
|
108
|
+
add_variable 'A_ARG_TYPE_Filter', 'string' # 2.5.7
|
109
|
+
add_variable 'A_ARG_TYPE_SortCriteria', 'string' # 2.5.8
|
110
|
+
add_variable 'A_ARG_TYPE_Index', 'ui4' # 2.5.9
|
111
|
+
add_variable 'A_ARG_TYPE_Count', 'ui4' # 2.5.10
|
112
|
+
add_variable 'A_ARG_TYPE_UpdateID', 'ui4' # 2.5.11
|
113
|
+
add_variable 'A_ARG_Type_TransferID', 'ui4' # 2.5.12
|
114
|
+
add_variable 'A_ARG_Type_TransferStatus', 'string' # 2.5.13
|
115
|
+
add_variable 'A_ARG_Type_TransferLength', 'string' # 2.5.14
|
116
|
+
add_variable 'A_ARG_Type_TransferTotal', 'string' # 2.5.15
|
117
|
+
add_variable 'A_ARG_TYPE_TagValueList', 'string' # 2.5.16
|
118
|
+
add_variable 'A_ARG_TYPE_URI', 'uri' # 2.5.17
|
119
|
+
add_variable 'SearchCapabilities', 'string' # 2.5.18
|
120
|
+
add_variable 'SortCapabilities', 'string' # 2.5.19
|
121
|
+
add_variable 'SystemUpdateID', 'ui4', nil, nil, true # 2.5.20
|
122
|
+
add_variable 'ContainerUpdateIDs', 'string', nil, nil, true # 2.5.21
|
123
|
+
|
124
|
+
def initialize(*args)
|
125
|
+
@directories = []
|
126
|
+
|
127
|
+
super
|
128
|
+
end
|
129
|
+
|
130
|
+
def on_init
|
131
|
+
@SystemUpdateID = 0
|
132
|
+
|
133
|
+
@object_count = 0
|
134
|
+
@objects = {}
|
135
|
+
@parents = {}
|
136
|
+
|
137
|
+
add_object 'root', -1
|
138
|
+
WEBrick::HTTPUtils::DefaultMimeTypes['mp3'] = 'audio/mpeg'
|
139
|
+
end
|
140
|
+
|
141
|
+
# :section: ContentServer implementation
|
142
|
+
|
143
|
+
##
|
144
|
+
# Allows the caller to incrementally browse the hierarchy of the content
|
145
|
+
# directory, including information listing the classes of objects available
|
146
|
+
# in any container.
|
147
|
+
|
148
|
+
def Browse(object_id, browse_flag, filter, starting_index, requested_count,
|
149
|
+
sort_criteria)
|
150
|
+
filter = filter.split ','
|
151
|
+
object_id = object_id.to_i
|
152
|
+
update_id = 0
|
153
|
+
|
154
|
+
case browse_flag
|
155
|
+
when 'BrowseMetadata' then
|
156
|
+
number_returned = 1
|
157
|
+
total_matches = 1
|
158
|
+
|
159
|
+
result = metadata_result object_id
|
160
|
+
when 'BrowseDirectChildren' then
|
161
|
+
number_returned, total_matches, result = children_result object_id
|
162
|
+
else
|
163
|
+
raise "unknown BrowseFlag #{browse_flag}"
|
164
|
+
end
|
165
|
+
|
166
|
+
[nil, result, number_returned, total_matches, update_id]
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# Returns the current value of the SystemUpdateID state variable. For use
|
171
|
+
# by clients that want to poll for any changes in the content directory
|
172
|
+
# instead of subscribing to events.
|
173
|
+
|
174
|
+
def GetSystemUpdateID
|
175
|
+
[nil, @SystemUpdateID]
|
176
|
+
end
|
177
|
+
|
178
|
+
# :section: Support implementation
|
179
|
+
|
180
|
+
##
|
181
|
+
# Adds object +name+ to the directory tree under +parent+
|
182
|
+
|
183
|
+
def add_object(name, parent)
|
184
|
+
object_id = @object_count
|
185
|
+
@object_count += 1
|
186
|
+
|
187
|
+
@objects[object_id] = name
|
188
|
+
@objects[name] = object_id
|
189
|
+
|
190
|
+
@parents[object_id] = parent
|
191
|
+
|
192
|
+
object_id
|
193
|
+
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# Adds +directory+ as a path searched by the content server
|
197
|
+
|
198
|
+
def add_directory(directory)
|
199
|
+
return self if @directories.include? directory
|
200
|
+
|
201
|
+
add_object directory, 0
|
202
|
+
@directories << directory
|
203
|
+
|
204
|
+
self
|
205
|
+
end
|
206
|
+
|
207
|
+
def children_result(object_id)
|
208
|
+
object = get_object object_id
|
209
|
+
|
210
|
+
children = if object_id == 0 then
|
211
|
+
@directories
|
212
|
+
else
|
213
|
+
Dir[File.join(object, '*')]
|
214
|
+
end
|
215
|
+
|
216
|
+
result = make_result do |xml|
|
217
|
+
children.each do |child|
|
218
|
+
child_id = get_object child, object_id
|
219
|
+
|
220
|
+
result_object xml, child, child_id, File.basename(child)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
[children.length, children.length, result]
|
225
|
+
end
|
226
|
+
|
227
|
+
def get_object(name, parent_id = nil)
|
228
|
+
if @objects.key? name then
|
229
|
+
@objects[name]
|
230
|
+
elsif parent_id.nil? then
|
231
|
+
raise Error, "object #{name} does not exist"
|
232
|
+
else
|
233
|
+
add_object name, parent_id
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def get_parent(object_id)
|
238
|
+
if @parents.key? object_id then
|
239
|
+
@parents[object_id]
|
240
|
+
else
|
241
|
+
raise Error, "invalid object id #{object_id}"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def item_class(mime_type)
|
246
|
+
case mime_type
|
247
|
+
when /^image/ then
|
248
|
+
'object.item.imageItem'
|
249
|
+
when /^audio/ then
|
250
|
+
'object.item.audioItem'
|
251
|
+
else
|
252
|
+
puts "unhandled mime type #{mime_type}"
|
253
|
+
'object.item'
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def make_result
|
258
|
+
result = []
|
259
|
+
|
260
|
+
xml = Builder::XmlMarkup.new :indent => 2, :target => result
|
261
|
+
xml.tag! 'DIDL-Lite',
|
262
|
+
'xmlns' => 'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/',
|
263
|
+
'xmlns:dc' => 'http://purl.org/dc/elements/1.1/',
|
264
|
+
'xmlns:upnp' => 'urn:schemas-upnp-org:metadata-1-0/upnp/' do
|
265
|
+
yield xml
|
266
|
+
end
|
267
|
+
|
268
|
+
result.join
|
269
|
+
end
|
270
|
+
|
271
|
+
def metadata_result(object_id)
|
272
|
+
object = get_object object_id
|
273
|
+
parent = get_parent object_id
|
274
|
+
|
275
|
+
title = File.basename object
|
276
|
+
|
277
|
+
make_result do |xml|
|
278
|
+
result_object xml, object, object_id, title
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def mount_extra(http_server)
|
283
|
+
super
|
284
|
+
|
285
|
+
@directories.each do |root|
|
286
|
+
root_id = get_object root
|
287
|
+
path = File.join service_path, root_id.to_s
|
288
|
+
|
289
|
+
http_server.mount path, WEBrick::HTTPServlet::FileHandler, root
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def resource(xml, object, mime_type, stat)
|
294
|
+
info = nil
|
295
|
+
url = nil
|
296
|
+
|
297
|
+
case mime_type
|
298
|
+
when /^audio\/(.*)/ then
|
299
|
+
pn = case $1
|
300
|
+
when 'mpeg' then
|
301
|
+
'MP3'
|
302
|
+
end
|
303
|
+
|
304
|
+
pn = "DLNA.ORG_PN=#{pn}"
|
305
|
+
|
306
|
+
additional = [pn, 'DLNA.ORG_OP=01', 'DLNA.ORG_CI=0'].compact.join ';'
|
307
|
+
|
308
|
+
info = ['http-get', '*', mime_type, additional]
|
309
|
+
when /^image\/(.*)/ then
|
310
|
+
pn = case $1
|
311
|
+
when 'jpeg' then
|
312
|
+
'JPEG_LRG'
|
313
|
+
end
|
314
|
+
|
315
|
+
pn = "DLNA.ORG_PN=#{pn}"
|
316
|
+
|
317
|
+
additional = [pn, 'DLNA.ORG_OP=01', 'DLNA.ORG_CI=0'].compact.join ';'
|
318
|
+
|
319
|
+
info = ['http-get', '*', mime_type, additional]
|
320
|
+
end
|
321
|
+
|
322
|
+
if info then
|
323
|
+
url = resource_url object
|
324
|
+
|
325
|
+
attributes = {
|
326
|
+
:protocolInfo => info.join(':'),
|
327
|
+
:size => stat.size,
|
328
|
+
}
|
329
|
+
|
330
|
+
xml.res attributes, URI.escape(url)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
##
|
335
|
+
# A URL to this object on this server. Correctly handles multi-homed
|
336
|
+
# servers.
|
337
|
+
|
338
|
+
def resource_url(object)
|
339
|
+
_, port, host, addr = Thread.current[:WEBrickSocket].addr
|
340
|
+
|
341
|
+
root = root_for object
|
342
|
+
root_id = get_object root
|
343
|
+
|
344
|
+
object = object.sub root, ''
|
345
|
+
|
346
|
+
File.join "http://#{addr}:#{port}", service_path, root_id.to_s, object
|
347
|
+
end
|
348
|
+
|
349
|
+
def result_object(xml, object, object_id, title)
|
350
|
+
if object_id == 0 then
|
351
|
+
result_container xml, object, object_id, @directories.length, title
|
352
|
+
elsif File.directory? object then
|
353
|
+
children = Dir[File.join(object, '*/')].length
|
354
|
+
|
355
|
+
result_container xml, object, object_id, children, title
|
356
|
+
else
|
357
|
+
result_item xml, object, object_id, title
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def result_container(xml, object, object_id, children, title)
|
362
|
+
xml.tag! 'container', :id => object_id, :parentID => get_parent(object_id),
|
363
|
+
:restricted => true, :childCount => children do
|
364
|
+
xml.dc :title, title
|
365
|
+
xml.upnp :class, 'object.container'
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def result_item(xml, object, object_id, title)
|
370
|
+
inn, out, = Open3.popen3('file', '-bI', object)
|
371
|
+
inn.close
|
372
|
+
|
373
|
+
mime_type = out.read.strip
|
374
|
+
|
375
|
+
stat = File.stat object
|
376
|
+
|
377
|
+
xml.tag! 'item', :id => object_id, :parentID => get_parent(object_id),
|
378
|
+
:restricted => true, :childCount => 0 do
|
379
|
+
xml.dc :title, title
|
380
|
+
xml.dc :date, stat.ctime.iso8601
|
381
|
+
xml.upnp :class, item_class(mime_type)
|
382
|
+
|
383
|
+
resource xml, object, mime_type, stat
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
##
|
388
|
+
# Returns the root for +object_id+
|
389
|
+
|
390
|
+
def root_for(object_id)
|
391
|
+
object_id = get_object object_id unless Integer === object_id
|
392
|
+
|
393
|
+
while (parent_id = get_parent(object_id)) != 0 do
|
394
|
+
object_id = parent_id
|
395
|
+
end
|
396
|
+
|
397
|
+
get_object object_id
|
398
|
+
end
|
399
|
+
|
400
|
+
end
|
401
|
+
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: UPnP-ContentDirectory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "1.0"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eric Hodel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIDNjCCAh6gAwIBAgIBADANBgkqhkiG9w0BAQUFADBBMRAwDgYDVQQDDAdkcmJy
|
14
|
+
YWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZFgNu
|
15
|
+
ZXQwHhcNMDcxMjIxMDIwNDE0WhcNMDgxMjIwMDIwNDE0WjBBMRAwDgYDVQQDDAdk
|
16
|
+
cmJyYWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZ
|
17
|
+
FgNuZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbbgLrGLGIDE76
|
18
|
+
LV/cvxdEzCuYuS3oG9PrSZnuDweySUfdp/so0cDq+j8bqy6OzZSw07gdjwFMSd6J
|
19
|
+
U5ddZCVywn5nnAQ+Ui7jMW54CYt5/H6f2US6U0hQOjJR6cpfiymgxGdfyTiVcvTm
|
20
|
+
Gj/okWrQl0NjYOYBpDi+9PPmaH2RmLJu0dB/NylsDnW5j6yN1BEI8MfJRR+HRKZY
|
21
|
+
mUtgzBwF1V4KIZQ8EuL6I/nHVu07i6IkrpAgxpXUfdJQJi0oZAqXurAV3yTxkFwd
|
22
|
+
g62YrrW26mDe+pZBzR6bpLE+PmXCzz7UxUq3AE0gPHbiMXie3EFE0oxnsU3lIduh
|
23
|
+
sCANiQ8BAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
24
|
+
BBS5k4Z75VSpdM0AclG2UvzFA/VW5DANBgkqhkiG9w0BAQUFAAOCAQEAHagT4lfX
|
25
|
+
kP/hDaiwGct7XPuVGbrOsKRVD59FF5kETBxEc9UQ1clKWngf8JoVuEoKD774dW19
|
26
|
+
bU0GOVWO+J6FMmT/Cp7nuFJ79egMf/gy4gfUfQMuvfcr6DvZUPIs9P/TlK59iMYF
|
27
|
+
DIOQ3DxdF3rMzztNUCizN4taVscEsjCcgW6WkUJnGdqlu3OHWpQxZBJkBTjPCoc6
|
28
|
+
UW6on70SFPmAy/5Cq0OJNGEWBfgD9q7rrs/X8GGwUWqXb85RXnUVi/P8Up75E0ag
|
29
|
+
14jEc90kN+C7oI/AGCBN0j6JnEtYIEJZibjjDJTSMWlUKKkj30kq7hlUC2CepJ4v
|
30
|
+
x52qPcexcYZR7w==
|
31
|
+
-----END CERTIFICATE-----
|
32
|
+
|
33
|
+
date: 2008-07-23 00:00:00 -07:00
|
34
|
+
default_executable:
|
35
|
+
dependencies:
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: UPnP
|
38
|
+
type: :runtime
|
39
|
+
version_requirement:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 1.1.0
|
45
|
+
version:
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: hoe
|
48
|
+
type: :development
|
49
|
+
version_requirement:
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.7.0
|
55
|
+
version:
|
56
|
+
description: A UPnP ContentDirectory service with some DLNA extensions. Currently this is a work in progress, and is only adequate for viewing images on a PlayStation 3.
|
57
|
+
email:
|
58
|
+
- drbrain@segment7.net
|
59
|
+
executables: []
|
60
|
+
|
61
|
+
extensions: []
|
62
|
+
|
63
|
+
extra_rdoc_files:
|
64
|
+
- History.txt
|
65
|
+
- Manifest.txt
|
66
|
+
- README.txt
|
67
|
+
files:
|
68
|
+
- History.txt
|
69
|
+
- Manifest.txt
|
70
|
+
- README.txt
|
71
|
+
- Rakefile
|
72
|
+
- lib/UPnP/service/content_directory.rb
|
73
|
+
has_rdoc: true
|
74
|
+
homepage: http://seattlerb.org/UPnP-ContentDirectory
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options:
|
77
|
+
- --main
|
78
|
+
- README.txt
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: "0"
|
86
|
+
version:
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: "0"
|
92
|
+
version:
|
93
|
+
requirements: []
|
94
|
+
|
95
|
+
rubyforge_project: seattlerb
|
96
|
+
rubygems_version: 1.2.0
|
97
|
+
signing_key:
|
98
|
+
specification_version: 2
|
99
|
+
summary: A UPnP ContentDirectory service with some DLNA extensions
|
100
|
+
test_files: []
|
101
|
+
|
metadata.gz.sig
ADDED