tivohmo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +17 -0
  4. data/.travis.yml +10 -0
  5. data/Gemfile +7 -0
  6. data/LICENSE.txt +514 -0
  7. data/README.md +50 -0
  8. data/Rakefile +7 -0
  9. data/bin/tivohmo +8 -0
  10. data/contrib/tivohmo.conf +17 -0
  11. data/contrib/tivohmo.plist +22 -0
  12. data/lib/tivohmo/adapters/filesystem/application.rb +23 -0
  13. data/lib/tivohmo/adapters/filesystem/file_item.rb +29 -0
  14. data/lib/tivohmo/adapters/filesystem/folder_container.rb +105 -0
  15. data/lib/tivohmo/adapters/filesystem.rb +3 -0
  16. data/lib/tivohmo/adapters/plex/application.rb +41 -0
  17. data/lib/tivohmo/adapters/plex/category.rb +72 -0
  18. data/lib/tivohmo/adapters/plex/episode.rb +26 -0
  19. data/lib/tivohmo/adapters/plex/metadata.rb +24 -0
  20. data/lib/tivohmo/adapters/plex/movie.rb +26 -0
  21. data/lib/tivohmo/adapters/plex/qualified_category.rb +51 -0
  22. data/lib/tivohmo/adapters/plex/season.rb +39 -0
  23. data/lib/tivohmo/adapters/plex/section.rb +48 -0
  24. data/lib/tivohmo/adapters/plex/show.rb +39 -0
  25. data/lib/tivohmo/adapters/plex/transcoder.rb +19 -0
  26. data/lib/tivohmo/adapters/plex.rb +11 -0
  27. data/lib/tivohmo/adapters/streamio/metadata.rb +26 -0
  28. data/lib/tivohmo/adapters/streamio/transcoder.rb +239 -0
  29. data/lib/tivohmo/adapters/streamio.rb +17 -0
  30. data/lib/tivohmo/api/application.rb +35 -0
  31. data/lib/tivohmo/api/container.rb +31 -0
  32. data/lib/tivohmo/api/item.rb +32 -0
  33. data/lib/tivohmo/api/metadata.rb +98 -0
  34. data/lib/tivohmo/api/node.rb +115 -0
  35. data/lib/tivohmo/api/server.rb +24 -0
  36. data/lib/tivohmo/api/transcoder.rb +39 -0
  37. data/lib/tivohmo/api.rb +7 -0
  38. data/lib/tivohmo/beacon.rb +69 -0
  39. data/lib/tivohmo/cli.rb +182 -0
  40. data/lib/tivohmo/logging.rb +50 -0
  41. data/lib/tivohmo/server/views/_container.builder +19 -0
  42. data/lib/tivohmo/server/views/_item.builder +57 -0
  43. data/lib/tivohmo/server/views/container.builder +26 -0
  44. data/lib/tivohmo/server/views/item_details.builder +153 -0
  45. data/lib/tivohmo/server/views/layout.builder +2 -0
  46. data/lib/tivohmo/server/views/layout.erb +10 -0
  47. data/lib/tivohmo/server/views/server.builder +18 -0
  48. data/lib/tivohmo/server/views/server_info.builder +7 -0
  49. data/lib/tivohmo/server/views/unsupported.erb +7 -0
  50. data/lib/tivohmo/server/views/video_formats.builder +10 -0
  51. data/lib/tivohmo/server.rb +306 -0
  52. data/lib/tivohmo/version.rb +3 -0
  53. data/lib/tivohmo.rb +5 -0
  54. data/spec/adapters/filesystem/application_spec.rb +19 -0
  55. data/spec/adapters/filesystem/file_item_spec.rb +33 -0
  56. data/spec/adapters/filesystem/folder_container_spec.rb +115 -0
  57. data/spec/adapters/plex/application_spec.rb +20 -0
  58. data/spec/adapters/plex/category_spec.rb +66 -0
  59. data/spec/adapters/plex/episode_spec.rb +22 -0
  60. data/spec/adapters/plex/metadata_spec.rb +24 -0
  61. data/spec/adapters/plex/movie_spec.rb +22 -0
  62. data/spec/adapters/plex/qualified_category_spec.rb +51 -0
  63. data/spec/adapters/plex/season_spec.rb +22 -0
  64. data/spec/adapters/plex/section_spec.rb +38 -0
  65. data/spec/adapters/plex/show_spec.rb +22 -0
  66. data/spec/adapters/plex/transcoder_spec.rb +27 -0
  67. data/spec/adapters/streamio/metadata_spec.rb +34 -0
  68. data/spec/adapters/streamio/transcoder_spec.rb +42 -0
  69. data/spec/api/application_spec.rb +63 -0
  70. data/spec/api/container_spec.rb +34 -0
  71. data/spec/api/item_spec.rb +53 -0
  72. data/spec/api/metadata_spec.rb +59 -0
  73. data/spec/api/node_spec.rb +178 -0
  74. data/spec/api/server_spec.rb +24 -0
  75. data/spec/api/transcoder_spec.rb +25 -0
  76. data/spec/beacon_spec.rb +87 -0
  77. data/spec/cli_spec.rb +227 -0
  78. data/spec/server_spec.rb +458 -0
  79. data/spec/spec_helper.rb +123 -0
  80. data/tivohmo.gemspec +46 -0
  81. metadata +416 -0
@@ -0,0 +1,458 @@
1
+ require_relative "spec_helper"
2
+ require 'rack/test'
3
+ require 'nokogiri'
4
+
5
+ Sinatra::Base.set :environment, :test
6
+
7
+ describe TivoHMO::Server do
8
+ include Rack::Test::Methods
9
+
10
+ let(:application) do
11
+ a = TestAPI::Application.new('a1')
12
+ a.metadata_class = TestAPI::Metadata
13
+ a.transcoder_class = TestAPI::Transcoder
14
+ a
15
+ end
16
+
17
+ let(:server) do
18
+ s = TivoHMO::API::Server.new
19
+ s.add_child(application)
20
+ s
21
+ end
22
+
23
+ # for Rack::Test
24
+ let(:app) { TivoHMO::Server.new(server) }
25
+
26
+ before(:each) do
27
+ make_api_tree(application, c1: ['i1'])
28
+ end
29
+
30
+ let(:container) { server.find('/a1/c1') }
31
+ let(:item) { server.find('/a1/c1/i1') }
32
+ let(:helpers) { (Class.new { include TivoHMO::Server::Helpers }).new }
33
+
34
+ describe "helpers" do
35
+
36
+ include Rack::Utils
37
+
38
+ describe "#item_url" do
39
+
40
+ it "uses title_path as url" do
41
+ expect(helpers.item_url(item)).to eq item.title_path
42
+ end
43
+
44
+ end
45
+
46
+ describe "#container_url" do
47
+
48
+ it "generates a url" do
49
+ url = helpers.container_url(container)
50
+ uri = URI(url)
51
+ expect(uri.path).to eq("/TiVoConnect")
52
+ expect(parse_query(uri.query)).to eq('Command' => 'QueryContainer',
53
+ 'Container' => container.title_path)
54
+ end
55
+
56
+ end
57
+
58
+ describe "#item_detail_url" do
59
+
60
+ it "generates a url" do
61
+ url = helpers.item_detail_url(item)
62
+ uri = URI(url)
63
+ expect(uri.path).to eq("/TiVoConnect")
64
+ expect(parse_query(uri.query)).to eq('Command' => 'TVBusQuery',
65
+ 'Container' => 'a1',
66
+ 'File' => 'c1/i1')
67
+ end
68
+
69
+ end
70
+
71
+ describe "#format_uuid" do
72
+
73
+ it "uses crc to format" do
74
+ uuid = SecureRandom.uuid
75
+ expect(helpers.format_uuid(uuid)).to eq(Zlib.crc32(uuid))
76
+ end
77
+
78
+ end
79
+
80
+ describe "#format_date" do
81
+
82
+ it "produces date as hex" do
83
+ date = Time.now
84
+ expect(helpers.format_date(date)).to eq("0x#{date.to_i.to_s(16)}")
85
+ end
86
+
87
+ end
88
+
89
+ describe "#format_iso_date" do
90
+
91
+ it "is empty string for nil" do
92
+ date = nil
93
+ expect(helpers.format_iso_date(date)).to eq("")
94
+ end
95
+
96
+ it "is formatted for real date" do
97
+ date = Time.now
98
+ expect(helpers.format_iso_date(date)).to eq(date.utc.strftime("%FT%T.%6N"))
99
+ end
100
+
101
+ end
102
+
103
+ describe "#format_iso_duration" do
104
+
105
+ it "is empty string for nil" do
106
+ expect(helpers.format_iso_duration(nil)).to eq("")
107
+ end
108
+
109
+ it "produces iso formatted string" do
110
+ duration = (1 * 86400) + (1 * 3600) + (1 * 60) + 1
111
+ expect(helpers.format_iso_duration(duration)).to eq('P1DT1H1M1S')
112
+ end
113
+
114
+ end
115
+
116
+ describe "#tivo_header" do
117
+
118
+ it "determines pad length" do
119
+ expect(helpers.pad(7, 4)).to eq(1)
120
+ end
121
+
122
+ it "tivo_header" do
123
+ expect(helpers).to receive(:builder).
124
+ with(:item_details, layout: true, locals: {item: item}).
125
+ and_return("<TvBusMarshalledStruct/>")
126
+ expect(helpers.tivo_header(item, 'video/x-tivo-mpeg')).to include('TvBusMarshalledStruct')
127
+ end
128
+
129
+ end
130
+
131
+ end
132
+
133
+ describe "TivoConnect" do
134
+
135
+ it "should fail for no command" do
136
+ get '/TiVoConnect'
137
+ expect(last_response.status).to eq(404)
138
+ end
139
+
140
+ it "should have a stub response for ResetServer" do
141
+ get '/TiVoConnect?Command=ResetServer'
142
+ expect(last_response.status).to eq(200)
143
+ end
144
+
145
+ it "should have a stub response for FlushServer" do
146
+ get '/TiVoConnect?Command=FlushServer'
147
+ expect(last_response.status).to eq(200)
148
+ end
149
+
150
+ it "should have a stub response for QueryItem" do
151
+ get '/TiVoConnect?Command=QueryItem'
152
+ expect(last_response.status).to eq(200)
153
+ end
154
+
155
+ it "should have a response for QueryServer" do
156
+ get '/TiVoConnect?Command=QueryServer'
157
+ expect(last_response.status).to eq(200)
158
+ doc = Nokogiri::XML(last_response.body)
159
+ name = doc.at_xpath('/TiVoServer/InternalName').content
160
+ expect(name).to eq('TivoHMO')
161
+
162
+ end
163
+
164
+ describe "QueryFormats" do
165
+
166
+ it "should have an error for QueryFormats with video" do
167
+ get '/TiVoConnect?Command=QueryFormats'
168
+ expect(last_response.status).to eq(404)
169
+ end
170
+
171
+ it "should have a response for QueryFormats" do
172
+ get '/TiVoConnect?Command=QueryFormats&SourceFormat=video%2Fx-tivo-mpeg'
173
+ expect(last_response.status).to eq(200)
174
+ doc = Nokogiri::XML(last_response.body)
175
+ cts = doc.xpath('/TiVoFormats/Format/ContentType')
176
+ expect(cts.collect(&:content)).to eq(['video/x-tivo-mpeg', 'video/x-tivo-mpeg-ts'])
177
+ end
178
+
179
+ end
180
+
181
+ describe "TVBusQuery" do
182
+
183
+ it "fails if it doesn't have params" do
184
+ get "/TiVoConnect?Command=TVBusQuery"
185
+ expect(last_response.status).to eq(404)
186
+ end
187
+
188
+ it "fails if it doesn't exist" do
189
+ get "/TiVoConnect?Command=TVBusQuery&Container=Foo&File=Bar"
190
+ expect(last_response.status).to eq(404)
191
+ end
192
+
193
+ it "provides item details" do
194
+ get "/TiVoConnect?Command=TVBusQuery&Container=a1&File=c1/i1"
195
+ expect(last_response.status).to eq(200)
196
+ doc = Nokogiri::XML(last_response.body)
197
+ expect(doc).to_not be_nil
198
+ end
199
+
200
+ end
201
+
202
+ describe "QueryContainer" do
203
+
204
+ it "displays root if it doesn't have params" do
205
+ get "/TiVoConnect?Command=QueryContainer"
206
+ expect(last_response.status).to eq(200)
207
+ doc = Nokogiri::XML(last_response.body)
208
+ title = doc.at_xpath("/TiVoContainer/Details/Title").content
209
+ expect(title).to eq(server.title)
210
+ end
211
+
212
+ it "uses server as root" do
213
+ get "/TiVoConnect?Command=QueryContainer&Container=/"
214
+ expect(last_response.status).to eq(200)
215
+ doc = Nokogiri::XML(last_response.body)
216
+ title = doc.at_xpath("/TiVoContainer/Details/Title").content
217
+ expect(title).to eq(server.title)
218
+ end
219
+
220
+ it "renders a container" do
221
+ container.add_child(TestAPI::Container.new("c2"))
222
+
223
+ get "/TiVoConnect?Command=QueryContainer&Container=/a1/c1"
224
+ expect(last_response.status).to eq(200)
225
+ doc = Nokogiri::XML(last_response.body)
226
+ expect(doc.at_xpath("/TiVoContainer/ItemStart").content).to eq("0")
227
+ expect(doc.at_xpath("/TiVoContainer/ItemCount").content).to eq("2")
228
+
229
+ expect(doc.at_xpath("/TiVoContainer/Details/TotalItems").content).to eq("2")
230
+ expect(doc.at_xpath("/TiVoContainer/Details/Title").content).to eq(container.title_path)
231
+ expect(doc.at_xpath("/TiVoContainer/Details/ContentType").content).to eq("x-tivo-container/folder")
232
+ expect(doc.at_xpath("/TiVoContainer/Details/SourceFormat").content).to eq("x-tivo-container/folder")
233
+ expect(doc.at_xpath("/TiVoContainer/Details/UniqueId").content).to eq(helpers.format_uuid(container.uuid).to_s)
234
+
235
+ expect(doc.xpath("/TiVoContainer/Item").size).to eq (2)
236
+
237
+ child_titles = doc.xpath("/TiVoContainer/Item/Details/Title").collect(&:content)
238
+ expect(child_titles).to match_array(["c2", "i1"])
239
+ end
240
+
241
+ it "shows details for child item" do
242
+ get "/TiVoConnect?Command=QueryContainer&Container=/a1/c1"
243
+ expect(last_response.status).to eq(200)
244
+ doc = Nokogiri::XML(last_response.body)
245
+
246
+ xml_item = doc.at_xpath("/TiVoContainer/Item")
247
+ expect(xml_item.at_xpath("Details/Title").content).to eq("i1")
248
+ expect(xml_item.at_xpath("Details/ContentType").content).to eq(item.content_type)
249
+ expect(xml_item.at_xpath("Details/SourceFormat").content).to eq(item.source_format)
250
+ expect(xml_item.at_xpath("Details/CaptureDate").content).to eq(helpers.format_date(item.created_at))
251
+ expect(xml_item.at_xpath("Details/UniqueId")).to be_nil
252
+ expect(xml_item.at_xpath("Details/TotalItems")).to be_nil
253
+ expect(xml_item.at_xpath("Details/LastCaptureDate")).to be_nil
254
+
255
+ # if metadata present
256
+ expect(xml_item.at_xpath("Details/SourceSize").content).to_not be_nil
257
+
258
+ xml_details = xml_item.at_xpath("Links/Content")
259
+ expect(xml_details.at_xpath("Url").content).to eq(helpers.item_url(item))
260
+ expect(xml_details.at_xpath("ContentType").content).to eq(item.content_type)
261
+
262
+ expect(xml_item.at_xpath("Links/CustomIcon").content).to_not be_nil
263
+
264
+ expect(xml_item.at_xpath("Links/TiVoVideoDetails/Url").content).to eq(helpers.item_detail_url(item))
265
+ end
266
+
267
+ it "shows details for child container" do
268
+ c2 = application.add_child(TestAPI::Container.new("c2"))
269
+ c3 = c2.add_child(TestAPI::Container.new("c3"))
270
+ c3 << TestAPI::Item.new('i2')
271
+ c3 << TestAPI::Item.new('i3')
272
+
273
+ get "/TiVoConnect?Command=QueryContainer&Container=/a1/c2"
274
+ expect(last_response.status).to eq(200)
275
+ doc = Nokogiri::XML(last_response.body)
276
+
277
+ xml_item = doc.at_xpath("/TiVoContainer/Item")
278
+ expect(xml_item.at_xpath("Details/Title").content).to eq("c3")
279
+ expect(xml_item.at_xpath("Details/ContentType").content).to eq(c3.content_type)
280
+ expect(xml_item.at_xpath("Details/SourceFormat").content).to eq(c3.source_format)
281
+ expect(xml_item.at_xpath("Details/UniqueId").content).to eq(helpers.format_uuid(c3.uuid).to_s)
282
+ expect(xml_item.at_xpath("Details/TotalItems").content).to eq("2")
283
+ expect(xml_item.at_xpath("Details/LastCaptureDate").content).to eq(helpers.format_date(c3.created_at))
284
+
285
+ xml_details = xml_item.at_xpath("Links/Content")
286
+ expect(xml_details.at_xpath("Url").content).to eq(helpers.container_url(c3))
287
+ expect(xml_details.at_xpath("ContentType").content).to eq("x-tivo-container/folder")
288
+
289
+ end
290
+
291
+ describe "sorting" do
292
+
293
+ before(:each) do
294
+ 3.times do |i|
295
+ container.add_child(TestAPI::Item.new("i#{i + 2}"))
296
+ end
297
+ end
298
+
299
+ it "displays sorted by title" do
300
+ get "/TiVoConnect?Command=QueryContainer&Container=/a1/c1&SortOrder=Title"
301
+ expect(last_response.status).to eq(200)
302
+ doc = Nokogiri::XML(last_response.body)
303
+
304
+ child_titles = doc.xpath("/TiVoContainer/Item/Details/Title").collect(&:content)
305
+ expect(child_titles).to eq(["i1", "i2", "i3", "i4"])
306
+ end
307
+
308
+ it "displays sorted by date" do
309
+ server.find("/a1/c1/i4").created_at = 4.hours.ago
310
+ server.find("/a1/c1/i3").created_at = 3.hours.ago
311
+ server.find("/a1/c1/i2").created_at = 2.hours.ago
312
+ server.find("/a1/c1/i1").created_at = 1.hour.ago
313
+ get "/TiVoConnect?Command=QueryContainer&Container=/a1/c1&SortOrder=CaptureDate"
314
+ expect(last_response.status).to eq(200)
315
+ doc = Nokogiri::XML(last_response.body)
316
+
317
+ child_titles = doc.xpath("/TiVoContainer/Item/Details/Title").collect(&:content)
318
+ expect(child_titles).to eq(["i4", "i3", "i2", "i1"])
319
+ end
320
+
321
+ it "honors direction" do
322
+ get "/TiVoConnect?Command=QueryContainer&Container=/a1/c1&SortOrder=!Title"
323
+ expect(last_response.status).to eq(200)
324
+ doc = Nokogiri::XML(last_response.body)
325
+
326
+ child_titles = doc.xpath("/TiVoContainer/Item/Details/Title").collect(&:content)
327
+ expect(child_titles).to eq(["i4", "i3", "i2", "i1"])
328
+ end
329
+
330
+ end
331
+
332
+ describe "pagination" do
333
+
334
+ before(:each) do
335
+ 19.times do |i|
336
+ container.add_child(TestAPI::Item.new("i#{i + 2}"))
337
+ end
338
+ end
339
+
340
+ it "displays first page" do
341
+ get "/TiVoConnect?Command=QueryContainer&Container=/a1/c1"
342
+ expect(last_response.status).to eq(200)
343
+ doc = Nokogiri::XML(last_response.body)
344
+
345
+ expect(doc.at_xpath("/TiVoContainer/ItemStart").content).to eq("0")
346
+ expect(doc.at_xpath("/TiVoContainer/ItemCount").content).to eq("8")
347
+
348
+ expect(doc.at_xpath("/TiVoContainer/Details/TotalItems").content).to eq("20")
349
+
350
+ expect(doc.xpath("/TiVoContainer/Item").size).to eq (8)
351
+
352
+ child_titles = doc.xpath("/TiVoContainer/Item/Details/Title").collect(&:content)
353
+ expect(child_titles).to match_array(["i1", "i2", "i3", "i4", "i5", "i6", "i7", "i8"])
354
+ end
355
+
356
+ it "honors ItemStart" do
357
+ get "/TiVoConnect?Command=QueryContainer&Container=/a1/c1&ItemStart=3"
358
+ expect(last_response.status).to eq(200)
359
+ doc = Nokogiri::XML(last_response.body)
360
+
361
+ expect(doc.at_xpath("/TiVoContainer/ItemStart").content).to eq("3")
362
+ expect(doc.at_xpath("/TiVoContainer/ItemCount").content).to eq("8")
363
+
364
+ expect(doc.at_xpath("/TiVoContainer/Details/TotalItems").content).to eq("20")
365
+
366
+ expect(doc.xpath("/TiVoContainer/Item").size).to eq (8)
367
+
368
+ child_titles = doc.xpath("/TiVoContainer/Item/Details/Title").collect(&:content)
369
+ expect(child_titles).to match_array(["i4", "i5", "i6", "i7", "i8", "i9", "i10", "i11"])
370
+ end
371
+
372
+ it "honors ItemCount" do
373
+ get "/TiVoConnect?Command=QueryContainer&Container=/a1/c1&ItemCount=3"
374
+ expect(last_response.status).to eq(200)
375
+ doc = Nokogiri::XML(last_response.body)
376
+
377
+ expect(doc.at_xpath("/TiVoContainer/ItemStart").content).to eq("0")
378
+ expect(doc.at_xpath("/TiVoContainer/ItemCount").content).to eq("3")
379
+
380
+ expect(doc.at_xpath("/TiVoContainer/Details/TotalItems").content).to eq("20")
381
+
382
+ expect(doc.xpath("/TiVoContainer/Item").size).to eq (3)
383
+
384
+ child_titles = doc.xpath("/TiVoContainer/Item/Details/Title").collect(&:content)
385
+ expect(child_titles).to match_array(["i1", "i2", "i3"])
386
+ end
387
+
388
+ it "displays page 2" do
389
+ get "/TiVoConnect?Command=QueryContainer&Container=/a1/c1&ItemStart=7"
390
+ expect(last_response.status).to eq(200)
391
+ doc = Nokogiri::XML(last_response.body)
392
+
393
+ expect(doc.at_xpath("/TiVoContainer/ItemStart").content).to eq("7")
394
+ expect(doc.at_xpath("/TiVoContainer/ItemCount").content).to eq("8")
395
+
396
+ expect(doc.at_xpath("/TiVoContainer/Details/TotalItems").content).to eq("20")
397
+
398
+ expect(doc.xpath("/TiVoContainer/Item").size).to eq (8)
399
+
400
+ child_titles = doc.xpath("/TiVoContainer/Item/Details/Title").collect(&:content)
401
+ expect(child_titles).to match_array(["i8", "i9", "i10", "i11", "i12", "i13", "i14", "i15"])
402
+ end
403
+
404
+ it "displays page using anchors" do
405
+ get "/TiVoConnect?Command=QueryContainer&Container=/a1/c1&AnchorItem=/a1/c1/i8&AnchorOffset=-1"
406
+ expect(last_response.status).to eq(200)
407
+ doc = Nokogiri::XML(last_response.body)
408
+
409
+ expect(doc.at_xpath("/TiVoContainer/ItemStart").content).to eq("7")
410
+ expect(doc.at_xpath("/TiVoContainer/ItemCount").content).to eq("8")
411
+
412
+ expect(doc.at_xpath("/TiVoContainer/Details/TotalItems").content).to eq("20")
413
+
414
+ expect(doc.xpath("/TiVoContainer/Item").size).to eq (8)
415
+
416
+ child_titles = doc.xpath("/TiVoContainer/Item/Details/Title").collect(&:content)
417
+ expect(child_titles).to match_array(["i8", "i9", "i10", "i11", "i12", "i13", "i14", "i15"])
418
+ end
419
+
420
+ it "honors anchor offset when displaying page using anchors" do
421
+ get "/TiVoConnect?Command=QueryContainer&Container=/a1/c1&AnchorItem=/a1/c1/i8&AnchorOffset=-8"
422
+ expect(last_response.status).to eq(200)
423
+ doc = Nokogiri::XML(last_response.body)
424
+
425
+ expect(doc.at_xpath("/TiVoContainer/ItemStart").content).to eq("0")
426
+ expect(doc.at_xpath("/TiVoContainer/ItemCount").content).to eq("8")
427
+
428
+ expect(doc.at_xpath("/TiVoContainer/Details/TotalItems").content).to eq("20")
429
+
430
+ expect(doc.xpath("/TiVoContainer/Item").size).to eq (8)
431
+
432
+ child_titles = doc.xpath("/TiVoContainer/Item/Details/Title").collect(&:content)
433
+ expect(child_titles).to match_array(["i1", "i2", "i3", "i4", "i5", "i6", "i7", "i8"])
434
+ end
435
+
436
+ end
437
+
438
+ end
439
+
440
+ end
441
+
442
+ describe "transcode" do
443
+
444
+ it "fails if it doesn't exist" do
445
+ get "/this/is/not/here"
446
+ expect(last_response.status).to eq(404)
447
+ end
448
+
449
+ it "sends transcoded data" do
450
+ get "/a1/c1/i1?Format=?video/x-tivo-mpeg"
451
+ expect(last_response.status).to eq(206)
452
+ expect(last_response.body).to include("/A1/C1/I1")
453
+ end
454
+
455
+ end
456
+
457
+ end
458
+
@@ -0,0 +1,123 @@
1
+
2
+ require 'rspec'
3
+ RSpec.configure do |config|
4
+ end
5
+
6
+ require 'coveralls'
7
+ Coveralls.wear!
8
+
9
+ require 'tivohmo/logging'
10
+ Logging.logger.root.level = :error
11
+
12
+ require 'tivohmo'
13
+ require 'open-uri'
14
+
15
+ def video_fixture(name)
16
+ fixture_names = {
17
+ tiny: 'MPEG4 by philips.mp4',
18
+ with_audio: 'test_qcif_200_aac_64.mp4'
19
+ }
20
+
21
+ file_name = fixture_names[name]
22
+ raise ArgumentError, "No fixture for :#{name}" unless file_name
23
+
24
+ base_url = 'http://samples.mplayerhq.hu/MPEG-4'
25
+ video_url = "#{base_url}/#{URI.escape(file_name)}"
26
+
27
+ cache_dir = '/tmp/video_fixtures'
28
+ video_file = "#{cache_dir}/#{file_name}"
29
+
30
+ if ! File.exist?(video_file)
31
+ puts "Downloading video fixture #{name} from #{video_url}"
32
+ FileUtils.mkdir_p(cache_dir)
33
+ File.write(video_file, open(video_url).read)
34
+ end
35
+
36
+ video_file
37
+ end
38
+
39
+ module TestAPI
40
+
41
+ class Application
42
+ include TivoHMO::API::Application
43
+ end
44
+
45
+ class Container
46
+ include TivoHMO::API::Container
47
+ end
48
+
49
+ class Item
50
+ include TivoHMO::API::Item
51
+ end
52
+
53
+ class Metadata
54
+ include TivoHMO::API::Metadata
55
+ end
56
+
57
+ class Transcoder
58
+ include TivoHMO::API::Transcoder
59
+ def transcoder_options; {}; end
60
+ def transcode(io, format); io << item.title_path.upcase; end
61
+ end
62
+ end
63
+
64
+ def make_api_tree(parent, *items)
65
+ parent = TivoHMO::API::Server.new unless parent
66
+ if parent.is_a?(TivoHMO::API::Server)
67
+ parent = parent.add_child(TestAPI::Application.new('a'))
68
+ end
69
+
70
+ items.each do |item|
71
+ if item.is_a?(Hash)
72
+ item.each do |name, child_items|
73
+ c = parent.add_child(TestAPI::Container.new(name.to_s))
74
+ make_api_tree(c, *child_items)
75
+ end
76
+ elsif item.is_a?(Enumerable)
77
+ make_api_tree(parent, *item)
78
+ else
79
+ parent.add_child(TestAPI::Item.new(item.to_s))
80
+ end
81
+ end
82
+
83
+ parent
84
+ end
85
+
86
+ def mktree(path, items)
87
+ items.each do |item|
88
+ if item.is_a?(Hash)
89
+ item.each do |dir, contents|
90
+ p = "#{path}/#{dir}"
91
+ FileUtils.mkdir p
92
+ mktree(p, contents)
93
+ end
94
+ else
95
+ p = "#{path}/#{item}"
96
+ FileUtils.touch p
97
+ end
98
+ end
99
+ end
100
+
101
+ def with_file_tree(*tree)
102
+ Dir.mktmpdir('tivohmo_test') do |dir|
103
+ mktree(dir, tree)
104
+ yield dir
105
+ end
106
+ end
107
+
108
+ def plex_stub(clazz, method_stubs={})
109
+ default_stubs = {
110
+ key: '/some/key',
111
+ title: 'Title',
112
+ updated_at: Time.now.to_i,
113
+ added_at: Time.now.to_i,
114
+ refresh: nil
115
+ }
116
+ d = double(clazz, default_stubs.merge(method_stubs))
117
+
118
+ allow(d).to receive(:is_a?) do |arg|
119
+ arg == clazz
120
+ end
121
+
122
+ d
123
+ end
data/tivohmo.gemspec ADDED
@@ -0,0 +1,46 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tivohmo/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tivohmo"
8
+ spec.version = TivoHMO::VERSION
9
+ spec.authors = ["Matt Conway"]
10
+ spec.email = ["matt@conwaysplace.com"]
11
+ spec.summary = %q{Ruby SDK for Tivo Home Media Option}
12
+ spec.description = %q{Allows one to author Tivo HMO applications using ruby}
13
+ spec.homepage = ""
14
+ spec.license = "LGPL"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "pry"
24
+ spec.add_development_dependency "pry-byebug"
25
+ spec.add_development_dependency "awesome_print"
26
+ spec.add_development_dependency "rspec"
27
+ spec.add_development_dependency "rack-test"
28
+ spec.add_development_dependency "nokogiri"
29
+
30
+ # core dependencies
31
+ spec.add_dependency "activesupport"
32
+ spec.add_dependency "gem_logger"
33
+ spec.add_dependency "logging"
34
+ spec.add_dependency "sigdump"
35
+ spec.add_dependency "clamp"
36
+ spec.add_dependency "sinatra"
37
+ spec.add_dependency "builder"
38
+ spec.add_dependency "puma"
39
+
40
+ # filesystem adapter dependencies, make optional?
41
+ spec.add_dependency "listen"
42
+ spec.add_dependency "streamio-ffmpeg"
43
+
44
+ # plex adapter dependencies, make optional?
45
+ spec.add_dependency "plex-ruby"
46
+ end