simple_pvr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/.rspec +1 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +133 -0
  4. data/LICENSE.txt +13 -0
  5. data/README.md +169 -0
  6. data/Rakefile +16 -0
  7. data/bin/pvr_server +10 -0
  8. data/bin/pvr_xmltv +18 -0
  9. data/features/channel_overview.feature +48 -0
  10. data/features/programme_search.feature +20 -0
  11. data/features/scheduling.feature +112 -0
  12. data/features/step_definitions/pvr_steps.rb +104 -0
  13. data/features/step_definitions/web_steps.rb +219 -0
  14. data/features/support/env.rb +28 -0
  15. data/features/support/paths.rb +17 -0
  16. data/features/week_overview.feature +39 -0
  17. data/lib/simple_pvr/ffmpeg.rb +22 -0
  18. data/lib/simple_pvr/hdhomerun.rb +103 -0
  19. data/lib/simple_pvr/hdhomerun_save.sh +10 -0
  20. data/lib/simple_pvr/model/channel.rb +72 -0
  21. data/lib/simple_pvr/model/database_initializer.rb +36 -0
  22. data/lib/simple_pvr/model/programme.rb +58 -0
  23. data/lib/simple_pvr/model/recording.rb +45 -0
  24. data/lib/simple_pvr/model/schedule.rb +33 -0
  25. data/lib/simple_pvr/pvr_initializer.rb +47 -0
  26. data/lib/simple_pvr/pvr_logger.rb +14 -0
  27. data/lib/simple_pvr/recorder.rb +25 -0
  28. data/lib/simple_pvr/recording_manager.rb +101 -0
  29. data/lib/simple_pvr/recording_planner.rb +72 -0
  30. data/lib/simple_pvr/scheduler.rb +124 -0
  31. data/lib/simple_pvr/server/app_controller.rb +13 -0
  32. data/lib/simple_pvr/server/base_controller.rb +94 -0
  33. data/lib/simple_pvr/server/channels_controller.rb +68 -0
  34. data/lib/simple_pvr/server/config.ru +8 -0
  35. data/lib/simple_pvr/server/programmes_controller.rb +46 -0
  36. data/lib/simple_pvr/server/rack_maps.rb +7 -0
  37. data/lib/simple_pvr/server/schedules_controller.rb +71 -0
  38. data/lib/simple_pvr/server/shows_controller.rb +63 -0
  39. data/lib/simple_pvr/server/status_controller.rb +11 -0
  40. data/lib/simple_pvr/server/upcoming_recordings_controller.rb +18 -0
  41. data/lib/simple_pvr/version.rb +3 -0
  42. data/lib/simple_pvr/xmltv_reader.rb +83 -0
  43. data/lib/simple_pvr.rb +22 -0
  44. data/public/css/bootstrap-responsive.min.css +9 -0
  45. data/public/css/bootstrap.min.css +9 -0
  46. data/public/css/simplepvr.css +11 -0
  47. data/public/img/glyphicons-halflings-white.png +0 -0
  48. data/public/img/glyphicons-halflings.png +0 -0
  49. data/public/index.html +55 -0
  50. data/public/js/angular/angular-resource.min.js +10 -0
  51. data/public/js/angular/angular.min.js +157 -0
  52. data/public/js/app.js +145 -0
  53. data/public/js/bootstrap/bootstrap.min.js +6 -0
  54. data/public/js/controllers.js +156 -0
  55. data/public/js/services.js +27 -0
  56. data/public/partials/about.html +5 -0
  57. data/public/partials/channels.html +41 -0
  58. data/public/partials/programme.html +20 -0
  59. data/public/partials/programmeListing.html +18 -0
  60. data/public/partials/schedule.html +80 -0
  61. data/public/partials/schedules.html +44 -0
  62. data/public/partials/search.html +17 -0
  63. data/public/partials/show.html +21 -0
  64. data/public/partials/shows.html +7 -0
  65. data/public/partials/status.html +6 -0
  66. data/simple_pvr.gemspec +30 -0
  67. data/spec/resources/channels.txt +11 -0
  68. data/spec/resources/programs-without-icon.xmltv +95 -0
  69. data/spec/resources/programs.xmltv +98 -0
  70. data/spec/simple_pvr/ffmpeg_spec.rb +26 -0
  71. data/spec/simple_pvr/hdhomerun_spec.rb +82 -0
  72. data/spec/simple_pvr/model/channel_spec.rb +114 -0
  73. data/spec/simple_pvr/model/programme_spec.rb +110 -0
  74. data/spec/simple_pvr/model/schedule_spec.rb +47 -0
  75. data/spec/simple_pvr/pvr_initializer_spec.rb +50 -0
  76. data/spec/simple_pvr/recorder_spec.rb +32 -0
  77. data/spec/simple_pvr/recording_manager_spec.rb +158 -0
  78. data/spec/simple_pvr/recording_planner_spec.rb +104 -0
  79. data/spec/simple_pvr/scheduler_spec.rb +201 -0
  80. data/spec/simple_pvr/xmltv_reader_spec.rb +49 -0
  81. data/test/config/jsTestDriver-scenario.conf +10 -0
  82. data/test/config/jsTestDriver.conf +12 -0
  83. data/test/config/jstd-scenario-adapter-config.js +6 -0
  84. data/test/filtersSpec.js +97 -0
  85. data/test/lib/angular/angular-mocks.js +1719 -0
  86. data/test/lib/angular/angular-scenario.js +25937 -0
  87. data/test/lib/angular/jstd-scenario-adapter.js +185 -0
  88. data/test/lib/angular/version.txt +1 -0
  89. data/test/lib/jasmine/MIT.LICENSE +20 -0
  90. data/test/lib/jasmine/index.js +180 -0
  91. data/test/lib/jasmine/jasmine-html.js +190 -0
  92. data/test/lib/jasmine/jasmine.css +166 -0
  93. data/test/lib/jasmine/jasmine.js +2476 -0
  94. data/test/lib/jasmine/jasmine_favicon.png +0 -0
  95. data/test/lib/jasmine/version.txt +1 -0
  96. data/test/lib/jasmine-jstd-adapter/JasmineAdapter.js +196 -0
  97. data/test/lib/jasmine-jstd-adapter/version.txt +1 -0
  98. data/test/lib/jstestdriver/JsTestDriver.jar +0 -0
  99. data/test/lib/jstestdriver/version.txt +1 -0
  100. data/test/scripts/test-server.sh +14 -0
  101. data/test/scripts/test.sh +8 -0
  102. metadata +342 -0
@@ -0,0 +1,98 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <tv generator-info-name="www.ontv.dk/xmltv">
3
+ <channel id="www.ontv.dk/tv/1">
4
+ <display-name lang="dk">DR1 DK</display-name>
5
+ <icon src="http://ontv.dk/imgs/epg/logos/dr1_big.png"/>
6
+ </channel>
7
+ <channel id="www.ontv.dk/tv/2">
8
+ <display-name lang="dk">DR2 DK</display-name>
9
+ <icon src="http://ontv.dk/imgs/epg/logos/dr2_big.png"/>
10
+ </channel>
11
+
12
+ <programme channel="www.ontv.dk/tv/1" start="20120717060000 +0200" stop="20120717061000 +0200">
13
+ <title lang="dk">Noddy</title>
14
+ <sub-title lang="dk">Bare vær dig selv, Noddy.</sub-title>
15
+ <desc lang="dk">Tegnefilm.
16
+ Her kommer Noddy - så kom ud og leg! Den lille dreng af træ har altid travlt med at køre sine venner rundt i Legebyen - og du kan altid høre, når han er på vej!</desc>
17
+ <category lang="EN">kids</category>
18
+ <category lang="EN">serie</category>
19
+ <episode-num system="xmltv_ns"> .2/12. </episode-num>
20
+ <icon src="http://ontv.dk//imgs/print_img.php?sti=/imgs/epg/channel/2012-07-17/1/11342497600.jpg&amp;height=300&amp;width=300"/>
21
+ </programme>
22
+ <programme channel="www.ontv.dk/tv/1" start="20120717061000 +0200" stop="20120717062000 +0200">
23
+ <title lang="dk">Sprutte-Patruljen</title>
24
+ <desc lang="dk">Spruttepatruljen og algesuppen.</desc>
25
+ <category lang="EN">kids</category>
26
+ <category lang="EN">serie</category>
27
+ </programme>
28
+ <programme channel="www.ontv.dk/tv/1" start="20120717062000 +0200" stop="20120717064000 +0200">
29
+ <title lang="dk">Kære Sebastian</title>
30
+ <sub-title lang="dk">Geparden skal have kød.</sub-title>
31
+ <desc lang="dk">Zoo's vildeste dyr. Sebastian Klein overfaldes og skubbes omkuld af en af zoo's vildeste dyr - gederne. Det er mad tid og de er sultne.</desc>
32
+ <category lang="EN">kids</category>
33
+ <category lang="EN">serie</category>
34
+ </programme>
35
+ <programme channel="www.ontv.dk/tv/1" start="20120717064000 +0200" stop="20120717065000 +0200">
36
+ <title lang="dk">Rasmus Klump på Nordpolen</title>
37
+ <desc lang="dk">Vi tager atter med Rasmus Klump og hans venner på eventyr.</desc>
38
+ <category lang="EN">kids</category>
39
+ </programme>
40
+ <programme channel="www.ontv.dk/tv/1" start="20120717065000 +0200" stop="20120717070000 +0200">
41
+ <title lang="dk">Olivia</title>
42
+ <sub-title lang="dk">Olivia på scenen.</sub-title>
43
+ <desc lang="dk">Animation med dansk tale.
44
+ Slå griseører og øjne ud, for modige Olivia er klar til at tage verden med storm! Den skæve, skøre og sjove gris Olivia er god til alt muligt, bl.a. at gøre folk rigtig trætte. Olivia kan nemlig som ingen anden prøve tøj fra skabet eller male billeder på væggen. Olivia har også startet sit eget band og sågar optrådt i cirkus.</desc>
45
+ <category lang="EN">kids</category>
46
+ <category lang="EN">serie</category>
47
+ <icon src="http://ontv.dk//imgs/print_img.php?sti=/imgs/epg/channel/2012-07-17/1/11342500600.jpg&amp;height=300&amp;width=300"/>
48
+ </programme>
49
+
50
+ <programme channel="www.ontv.dk/tv/2" start="20120717080500 +0200" stop="20120717120000 +0200">
51
+ <title lang="dk">Morgenandagten på DR2</title>
52
+ <desc lang="dk">DR2 samsender med P2 Klassisk: Morgenandagten fra Københavns Domkirke. Den direkte radiotransmission af Morgenandagten ledsages af stemningsfulde billeder fra kirken samt kobbertryk af billedkunstneren Robert Jacobsen i anledning af 100-året for hans fødsel. Der er mulighed for at følge med i tekstlæsninger og salmesang, idet andagten tekstes.
53
+ www.dr.dk/tro</desc>
54
+ </programme>
55
+ <programme channel="www.ontv.dk/tv/2" start="20120717120000 +0200" stop="20120717123000 +0200">
56
+ <title lang="dk">Black Business</title>
57
+ <desc lang="dk">Hvis du har den rigtige idé, mod og eventyrlyst, kan du gøre store forretninger og tjene mange penge i Afrika. For Afrika er økonomisk set blandt de hurtigste voksende regioner i verden. Det sker lige nu, og det gælder om at komme først. Esben Sørensen er en rødhåret dansker på 30 år, der er vokset op delvist i Afrika og delvist i Danmark. Han har et pladeselskab, hvor han indtil nu kun har udgivet en enkelt plade - med sig selv. Drømmen er at Caravan Records bliver det største pladeselskab i Østafrika, og det er absolut ikke urealistisk, for Østafrika er fyldt med musikalsk talent, markedet er gigantisk og afrikanernes købekraft stiger.
58
+ Sendt første gang 25.01.11</desc>
59
+ <category lang="EN">documentary</category>
60
+ <category lang="EN">serie</category>
61
+ <episode-num system="xmltv_ns"> .1/4. </episode-num>
62
+ </programme>
63
+ <programme channel="www.ontv.dk/tv/2" start="20120717123000 +0200" stop="20120717130000 +0200">
64
+ <title lang="dk">Solens mad</title>
65
+ <desc lang="dk">Vi skal med på en rejse til madens og vinens Italien, vi skal på besøg hos nogle af de mange bønder i landet, som ikke går på kompromis med den gode smag og som tilhører den stadigt voksende bevægelse af producenter, der har en stor forståelse for, at miljø, klima og bæredygtighed er hjørnesten i alle produkter.
66
+ Overgødning og miljøgifte har ødelagt mange af Europas indsøer. I dag skal vi til Trasimeno-søen i Umbrien, den fjerdestørste indsø i Italien. Her lever fiskere og bønder side om side, begge parter meget bevidste om at bevare vandet og jorden her i landets grønne hjerte.
67
+ Nordvision fra Sverige.</desc>
68
+ <category lang="EN">entertainment</category>
69
+ <category lang="EN">serie</category>
70
+ <episode-num system="xmltv_ns"> .2/6. </episode-num>
71
+ </programme>
72
+ <programme channel="www.ontv.dk/tv/2" start="20120717130000 +0200" stop="20120717133000 +0200">
73
+ <title lang="dk">Øl i Frilandshaven</title>
74
+ <desc lang="dk">Sommerens bedste bryg bliver lavet over åben ild, og i udekøkkenet står menuen på kalvehaler i mørkt øl og jordbær i balsamico.
75
+ Sendt første gang 16.08.06</desc>
76
+ <category lang="EN">entertainment</category>
77
+ <credits>
78
+ <actor role="Organizer">Jørgen Skouboe</actor>
79
+ <actor role="Organizer">Anne Hjernøe</actor>
80
+ </credits>
81
+ </programme>
82
+ <programme channel="www.ontv.dk/tv/2" start="20120717133000 +0200" stop="20120717142500 +0200">
83
+ <title lang="dk">Intelligence - i hemmelig tjeneste</title>
84
+ <sub-title lang="dk">Italiensk actionserie fra 2009.</sub-title>
85
+ <desc lang="dk">Den tidligere jægersoldat Marco Tancredis hustru, Lidia, er blevet skudt. Marco er sikker på, at det var overlagt mord, og han er parat til at gøre hvad som helst for at finde ud af, hvem der står bag. Snart gør han en forbløffende opdagelse - nemlig at Lidia arbejdede for den italienske efterretningstjeneste. Men hvad arbejdede hun på? Og hvorfor skulle hun slås ihjel?</desc>
86
+ <category lang="EN">serie</category>
87
+ <episode-num system="xmltv_ns"> .1/12. </episode-num>
88
+ <icon src="http://ontv.dk//imgs/print_img.php?sti=/imgs/epg/channel/2012-07-17/2/21342524600.jpg&amp;height=300&amp;width=300"/>
89
+ </programme>
90
+ <programme channel="www.ontv.dk/tv/2" start="20120717142500 +0200" stop="20120717152000 +0200">
91
+ <title lang="dk">Intelligence - i hemmelig tjeneste</title>
92
+ <sub-title lang="dk">Italiensk actionserie fra 2009.</sub-title>
93
+ <desc lang="dk">Den tidligere jægersoldat Marco Tancredis hustru er blevet myrdet. Marco har opdaget, at hun arbejdede for den italienske efterretningstjeneste, og nu har han selv ladet sig ansætte som efterretningsagent for at finde frem til sandheden om hendes død. Marcos jagt på svar bringer ham til Rumænien - og ansigt til ansigt med en kvinde, der er ude på sit eget private hævntogt.</desc>
94
+ <category lang="EN">serie</category>
95
+ <episode-num system="xmltv_ns"> .2/12. </episode-num>
96
+ <icon src="http://ontv.dk//imgs/print_img.php?sti=/imgs/epg/channel/2012-07-17/2/21342527900.jpg&amp;height=300&amp;width=300"/>
97
+ </programme>
98
+ </tv>
@@ -0,0 +1,26 @@
1
+ require 'simple_pvr'
2
+
3
+ describe SimplePvr::Ffmpeg do
4
+ it 'spawns an ffmpeg process for creating thumbnails' do
5
+ Process.should_receive(:spawn)
6
+ Process.should_receive(:detach)
7
+
8
+ SimplePvr::Ffmpeg.create_thumbnail_for('path/to/show')
9
+ end
10
+
11
+ it 'spawns an ffmpeg process for transcoding to WebM' do
12
+ File.should_receive(:exists?).with('path/to/show/stream.webm').and_return(false)
13
+ Process.should_receive(:spawn)
14
+ Process.should_receive(:detach)
15
+
16
+ SimplePvr::Ffmpeg.transcode_to_webm('path/to/show')
17
+ end
18
+
19
+ it 'does not start ffmpeg for transcoding to WebM if WebM file already exists' do
20
+ File.should_receive(:exists?).with('path/to/show/stream.webm').and_return(true)
21
+ Process.should_not_receive(:spawn)
22
+ Process.should_not_receive(:detach)
23
+
24
+ SimplePvr::Ffmpeg.transcode_to_webm('path/to/show')
25
+ end
26
+ end
@@ -0,0 +1,82 @@
1
+ require 'simple_pvr'
2
+
3
+ describe SimplePvr::HDHomeRun do
4
+ before do
5
+ @pipe = double('pipe')
6
+ IO.should_receive(:popen).with('hdhomerun_config discover').and_yield(@pipe)
7
+ FileUtils.stub(:exists? => false)
8
+ end
9
+
10
+ context 'when initializing' do
11
+ it 'discovers a device when present' do
12
+ @pipe.stub(:read => 'hdhomerun device ABCDEF01 found at 10.0.0.4')
13
+
14
+ SimplePvr::HDHomeRun.new.device_id.should == 'ABCDEF01'
15
+ end
16
+
17
+ it 'raises an exception when no device is present' do
18
+ @pipe.stub(:read => 'no devices found')
19
+
20
+ expect { SimplePvr::HDHomeRun.new }.to raise_error(Exception, 'No device found: no devices found')
21
+ end
22
+ end
23
+
24
+ context 'when initialized' do
25
+ before do
26
+ @pipe.stub(:read => 'hdhomerun device ABCDEF01 found at 10.0.0.4')
27
+ @hdhomerun = SimplePvr::HDHomeRun.new
28
+ @hdhomerun.stub(:tuner_control_file).with(0).and_return('tuner0.lock')
29
+ @hdhomerun.stub(:tuner_control_file).with(1).and_return('tuner1.lock')
30
+ @file = File.open(File.dirname(__FILE__) + '/../resources/channels.txt', 'r:UTF-8')
31
+ end
32
+
33
+ after do
34
+ @file.close
35
+ end
36
+
37
+ it 'can do a channel scan' do
38
+ @hdhomerun.should_receive(:system).with('hdhomerun_config ABCDEF01 scan /tuner0 channels.txt')
39
+ SimplePvr::Model::Channel.should_receive(:clear)
40
+ File.should_receive(:open).with('channels.txt', 'r:UTF-8').and_yield(@file)
41
+ SimplePvr::Model::Channel.should_receive(:add).with('DR K', 282000000, 1098)
42
+ SimplePvr::Model::Channel.should_receive(:add).with('DR HD', 282000000, 1165)
43
+
44
+ @hdhomerun.scan_for_channels
45
+ end
46
+
47
+ it 'can start recording on one tuner' do
48
+ @hdhomerun.should_receive(:system).with("hdhomerun_config ABCDEF01 set /tuner0/channel auto:282000000")
49
+ @hdhomerun.should_receive(:system).with("hdhomerun_config ABCDEF01 set /tuner0/program 1098")
50
+ @hdhomerun.should_receive(:spawn) #... with a lot of arguments ...
51
+ FileUtils.should_receive(:touch).with('tuner0.lock')
52
+
53
+ @hdhomerun.start_recording(0, 282000000, 1098, 'test directory')
54
+ end
55
+
56
+ it 'can stop recording on one tuner' do
57
+ @hdhomerun.stub(:system)
58
+ @hdhomerun.stub(:spawn => 32)
59
+
60
+ FileUtils.should_receive(:touch).with('tuner0.lock')
61
+ FileUtils.should_receive(:rm).with('tuner0.lock')
62
+ Process.should_receive(:wait).with(32)
63
+ @hdhomerun.should_receive(:system).with("hdhomerun_config ABCDEF01 set /tuner0/channel none")
64
+
65
+ @hdhomerun.start_recording(0, 282000000, 1098, 'test directory')
66
+ @hdhomerun.stop_recording(0)
67
+ end
68
+
69
+ it 'can start and stop recording on another tuner' do
70
+ @hdhomerun.should_receive(:system).with("hdhomerun_config ABCDEF01 set /tuner1/channel auto:282000000")
71
+ @hdhomerun.should_receive(:system).with("hdhomerun_config ABCDEF01 set /tuner1/program 1098")
72
+ @hdhomerun.stub(:spawn => 32)
73
+ FileUtils.should_receive(:touch).with('tuner1.lock')
74
+ FileUtils.should_receive(:rm).with('tuner1.lock')
75
+ Process.should_receive(:wait).with(32)
76
+ @hdhomerun.should_receive(:system).with("hdhomerun_config ABCDEF01 set /tuner1/channel none")
77
+
78
+ @hdhomerun.start_recording(1, 282000000, 1098, 'test directory')
79
+ @hdhomerun.stop_recording(1)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,114 @@
1
+ require 'simple_pvr'
2
+
3
+ describe SimplePvr::Model::Channel do
4
+ Channel, Programme = SimplePvr::Model::Channel, SimplePvr::Model::Programme
5
+
6
+ before :all do
7
+ SimplePvr::Model::DatabaseInitializer.prepare_for_test
8
+ end
9
+
10
+ before :each do
11
+ SimplePvr::Model::DatabaseInitializer.clear
12
+ end
13
+
14
+ it 'can insert channels' do
15
+ 3.times {|i| Channel.add("Channel #{i}", 23000000, 1098) }
16
+
17
+ Channel.all.length.should == 3
18
+ end
19
+
20
+ it 'can clear channels' do
21
+ 3.times {|i| Channel.add("Channel #{i}", 23000000, 1098) }
22
+ Channel.clear
23
+
24
+ Channel.all.length.should == 0
25
+ end
26
+
27
+ it 'clears all programmes when clearing channels' do
28
+ channel = Channel.add("DR 1", 23000000, 1098)
29
+ 3.times { Programme.add(channel, 'Title', 'Subtitle', 'Description', Time.local(2012, 7, 17, 20, 30), 50.minutes, nil) }
30
+ Channel.clear
31
+
32
+ Programme.all.length.should == 0
33
+ end
34
+
35
+ it 'can find channels' do
36
+ Channel.add('Known channel', 23000000, 1098)
37
+
38
+ channel = Channel.with_name('Known channel')
39
+ channel.name.should == 'Known channel'
40
+ channel.frequency.should == 23000000
41
+ channel.channel_id.should == 1098
42
+ end
43
+
44
+ it 'complains when asked for non-existing channel' do
45
+ expect { Channel.with_name('unknown') }.to raise_error "Unknown channel: 'unknown'"
46
+ end
47
+
48
+ it 'can fetch a channel along with the current and next 3 programmes' do
49
+ channel = Channel.create(name: 'Channel 1')
50
+
51
+ now = Time.now
52
+ ten_minutes = 10.minutes.to_i
53
+ current = Programme.create(channel: channel, title: 'Current programme on channel 1', subtitle: 'Current programme subtitle', start_time: now.advance(minutes: -3), duration: ten_minutes)
54
+ programme_2 = Programme.create(channel: channel, title: 'Next programme', subtitle: 'Next programme subtitle', start_time: now.advance(minutes: 7), duration: ten_minutes)
55
+ programme_3 = Programme.create(channel: channel, title: 'Programme 3', start_time: now.advance(minutes: 17), duration: ten_minutes)
56
+ programme_4 = Programme.create(channel: channel, title: 'Programme 4', start_time: now.advance(minutes: 27), duration: ten_minutes)
57
+
58
+ channel_and_programmes = Channel.with_current_programmes(channel.id)
59
+
60
+ channel_and_programmes[:channel].should == channel
61
+ channel_and_programmes[:current_programme].should == current
62
+ channel_and_programmes[:upcoming_programmes].should == [programme_2, programme_3, programme_4]
63
+ end
64
+
65
+ it 'does not fetch programmes for hidden channels' do
66
+ channel = Channel.create(name: 'Channel 1')
67
+ channel.hidden = true
68
+ channel.save!
69
+
70
+ now = Time.now
71
+ ten_minutes = 10.minutes.to_i
72
+ current = Programme.create(channel: channel, title: 'Current programme on channel 1', subtitle: 'Current programme subtitle', start_time: now.advance(minutes: -3), duration: ten_minutes)
73
+ programme_2 = Programme.create(channel: channel, title: 'Next programme', subtitle: 'Next programme subtitle', start_time: now.advance(minutes: 7), duration: ten_minutes)
74
+
75
+ channel_and_programmes = Channel.with_current_programmes(channel.id)
76
+
77
+ channel_and_programmes[:channel].should == channel
78
+ channel_and_programmes[:current_programme].should be_nil
79
+ channel_and_programmes[:upcoming_programmes].should == []
80
+ end
81
+
82
+ it 'can fetch all channels, along with the current and next 3 programmes' do
83
+ channel_1 = Channel.add('Channel 1', 23000000, 1098)
84
+ channel_2 = Channel.add('Channel 2', 23000000, 1098)
85
+ channel_3 = Channel.add('Channel 3', 23000000, 1098)
86
+
87
+ now = Time.now
88
+ ten_minutes = 10.minutes.to_i
89
+ current_on_channel_1 = Programme.create(channel: channel_1, title: 'Current programme on channel 1', subtitle: 'Current programme subtitle', start_time: now.advance(minutes: -3), duration: ten_minutes)
90
+ programme_2_on_channel_1 = Programme.create(channel: channel_1, title: 'Next programme', subtitle: 'Next programme subtitle', start_time: now.advance(minutes: 7), duration: ten_minutes)
91
+ programme_3_on_channel_1 = Programme.create(channel: channel_1, title: 'Programme 3', start_time: now.advance(minutes: 17), duration: ten_minutes)
92
+ programme_4_on_channel_1 = Programme.create(channel: channel_1, title: 'Programme 4', start_time: now.advance(minutes: 27), duration: ten_minutes)
93
+ programme_5_on_channel_1 = Programme.create(channel: channel_1, title: 'Programme 5', start_time: now.advance(minutes: 37), duration: ten_minutes)
94
+
95
+ programme_1_on_channel_2 = Programme.create(channel: channel_2, title: 'Next programme on channel 2', start_time: now.advance(minutes: 17), duration: ten_minutes)
96
+
97
+ old_programme_on_channel_3 = Programme.create(channel: channel_2, title: 'Obsolete programme', start_time: now.advance(minutes: -20), duration: ten_minutes)
98
+
99
+ channels = Channel.all_with_current_programmes
100
+ channels.length.should == 3
101
+
102
+ channels[0][:channel].should == channel_1
103
+ channels[0][:current_programme].should == current_on_channel_1
104
+ channels[0][:upcoming_programmes].should == [programme_2_on_channel_1, programme_3_on_channel_1, programme_4_on_channel_1]
105
+
106
+ channels[1][:channel].should == channel_2
107
+ channels[1][:current_programme].should be_nil
108
+ channels[1][:upcoming_programmes].should == [programme_1_on_channel_2]
109
+
110
+ channels[2][:channel].should == channel_3
111
+ channels[2][:current_programme].should be_nil
112
+ channels[2][:upcoming_programmes].should == []
113
+ end
114
+ end
@@ -0,0 +1,110 @@
1
+ require 'simple_pvr'
2
+
3
+ describe SimplePvr::Model::Programme do
4
+ Channel, Programme = SimplePvr::Model::Channel, SimplePvr::Model::Programme
5
+
6
+ before :all do
7
+ SimplePvr::Model::DatabaseInitializer.prepare_for_test
8
+ end
9
+
10
+ before :each do
11
+ SimplePvr::Model::DatabaseInitializer.clear
12
+ end
13
+
14
+ before do
15
+ @dr_1 = Channel.add('DR 1', 23000000, 1098)
16
+ @dr_2 = Channel.add('DR 2', 24000000, 1099)
17
+ end
18
+
19
+ it 'can insert programmes' do
20
+ 3.times { Programme.add(@dr_1, 'Title', 'Subtitle', 'Description', Time.local(2012, 7, 17, 20, 30), 50.minutes, ' .4/12. ') }
21
+
22
+ Programme.all.length.should == 3
23
+ end
24
+
25
+ it 'can clear all programmes' do
26
+ 3.times { Programme.add(@dr_1, 'Title', 'Subtitle', 'Description', Time.local(2012, 7, 17, 20, 30), 50.minutes, nil) }
27
+ Programme.clear
28
+
29
+ Programme.all.length.should == 0
30
+ end
31
+
32
+ it 'can find all programmes with a certain title' do
33
+ Programme.add(@dr_2, 'Interesting', 'Second', 'Description', Time.local(2012, 7, 24, 20, 30), 50.minutes, nil)
34
+ Programme.add(@dr_1, 'Interesting', 'First', 'Description', Time.local(2012, 7, 17, 20, 30), 50.minutes, nil)
35
+ Programme.add(@dr_1, 'Uninteresting', 'Subtitle', 'Description', Time.local(2012, 7, 24, 20, 30), 50.minutes, nil)
36
+
37
+ programmes = Programme.with_title('Interesting')
38
+ programmes.length.should == 2
39
+
40
+ programmes[0].channel.should == @dr_1
41
+ programmes[0].title.should == 'Interesting'
42
+ programmes[0].subtitle.should == 'First'
43
+ programmes[0].description.should == 'Description'
44
+ programmes[0].start_time.should == Time.local(2012, 7, 17, 20, 30)
45
+ programmes[0].duration.should == 50.minutes
46
+
47
+ programmes[1].channel.should == @dr_2
48
+ programmes[1].title.should == 'Interesting'
49
+ programmes[1].subtitle.should == 'Second'
50
+ end
51
+
52
+ it 'can find all programmes with a certain title for a specific channel' do
53
+ Programme.add(@dr_1, 'Interesting', 'Second', 'Description', Time.local(2012, 7, 24, 20, 30), 50.minutes, nil)
54
+ Programme.add(@dr_1, 'Interesting', 'First', 'Description', Time.local(2012, 7, 17, 20, 30), 50.minutes, nil)
55
+ Programme.add(@dr_2, 'Interesting', '...but on wrong channel...', 'Description', Time.local(2012, 7, 24, 20, 30), 50.minutes, nil)
56
+ Programme.add(@dr_1, 'Uninteresting', 'Subtitle', 'Description', Time.local(2012, 7, 24, 20, 30), 50.minutes, nil)
57
+
58
+ programmes = Programme.on_channel_with_title(@dr_1, 'Interesting')
59
+ programmes.length.should == 2
60
+
61
+ programmes[0].channel.should == @dr_1
62
+ programmes[0].title.should == 'Interesting'
63
+ programmes[0].subtitle.should == 'First'
64
+
65
+ programmes[1].channel.should == @dr_1
66
+ programmes[1].title.should == 'Interesting'
67
+ programmes[1].subtitle.should == 'Second'
68
+ end
69
+
70
+ it 'can find titles containing a certain string' do
71
+ Programme.add(@dr_1, 'First programme', '', '', Time.local(2012, 7, 24, 20, 30), 50.minutes, nil)
72
+ Programme.add(@dr_2, 'Second programme', '', '', Time.local(2012, 7, 17, 20, 30), 50.minutes, nil)
73
+ Programme.add(@dr_1, 'Uninteresting', '', '', Time.local(2012, 7, 24, 20, 30), 50.minutes, nil)
74
+
75
+ titles = Programme.titles_containing('programme')
76
+ titles.should == ['First programme', 'Second programme']
77
+ end
78
+
79
+ it 'gives no duplicates as title search' do
80
+ Programme.add(@dr_1, 'First programme', '', '', Time.local(2012, 7, 24, 20, 30), 50.minutes, nil)
81
+ Programme.add(@dr_1, 'First programme', '', '', Time.local(2012, 7, 24, 20, 30), 50.minutes, nil)
82
+ Programme.add(@dr_2, 'Second programme', '', '', Time.local(2012, 7, 17, 20, 30), 50.minutes, nil)
83
+
84
+ titles = Programme.titles_containing('programme')
85
+ titles.should == ['First programme', 'Second programme']
86
+ end
87
+
88
+ it 'finds at most 8 titles containing a certain string' do
89
+ 20.times {|i| Programme.add(@dr_1, "Programme #{i}", '', '', Time.local(2012, 7, 24, 20, 30), 50.minutes, nil) }
90
+
91
+ titles = Programme.titles_containing('programme')
92
+ titles.length.should == 8
93
+ end
94
+
95
+ it 'can find programmes with titles containing a certain string, ordered by start time' do
96
+ first_programme = Programme.add(@dr_1, 'First programme', '', '', Time.local(2012, 7, 24, 20, 30), 50.minutes, nil)
97
+ second_programme = Programme.add(@dr_2, 'Second programme', '', '', Time.local(2012, 7, 17, 20, 30), 50.minutes, nil)
98
+ Programme.add(@dr_1, 'Uninteresting', '', '', Time.local(2012, 7, 24, 20, 30), 50.minutes, nil)
99
+
100
+ titles = Programme.with_title_containing('programme')
101
+ titles.should == [second_programme, first_programme]
102
+ end
103
+
104
+ it 'finds at most 20 programmes with titles containing a certain string' do
105
+ 30.times {|i| Programme.add(@dr_1, "Programme #{i}", '', '', Time.local(2012, 7, 24, 20, 30), 50.minutes, nil) }
106
+
107
+ titles = Programme.with_title_containing('programme')
108
+ titles.length.should == 20
109
+ end
110
+ end
@@ -0,0 +1,47 @@
1
+ require 'simple_pvr'
2
+
3
+ describe SimplePvr::Model::Schedule do
4
+ Channel, Schedule = SimplePvr::Model::Channel, SimplePvr::Model::Schedule
5
+
6
+ before :all do
7
+ SimplePvr::Model::DatabaseInitializer.prepare_for_test
8
+ end
9
+
10
+ before :each do
11
+ SimplePvr::Model::DatabaseInitializer.clear
12
+ @dr_1 = Channel.add('DR 1', 23000000, 1098)
13
+ end
14
+
15
+ it 'can save a schedule with a title' do
16
+ Schedule.add_specification(:title => 'Sports')
17
+
18
+ schedules = Schedule.all
19
+ schedules.length.should == 1
20
+ schedules[0].type.should == :specification
21
+ schedules[0].title.should == 'Sports'
22
+ schedules[0].channel.should be_nil
23
+ end
24
+
25
+ it 'can save a schedule with a title and a channel' do
26
+ Schedule.add_specification(:title => 'Sports', :channel => @dr_1)
27
+
28
+ schedules = Schedule.all
29
+ schedules.length.should == 1
30
+ schedules[0].type.should == :specification
31
+ schedules[0].title.should == 'Sports'
32
+ schedules[0].channel.name.should == 'DR 1'
33
+ end
34
+
35
+ it 'can save a schedule with a title and a channel and a start_time' do
36
+ start_time = Time.local(2012, 7, 10, 20, 50)
37
+ Schedule.add_specification(title: 'Sports', channel: @dr_1, start_time: start_time)
38
+
39
+ schedules = Schedule.all
40
+ schedules.length.should == 1
41
+ schedules[0].type.should == :specification
42
+ schedules[0].title.should == 'Sports'
43
+ schedules[0].channel.name.should == 'DR 1'
44
+ schedules[0].start_time.should == start_time
45
+
46
+ end
47
+ end
@@ -0,0 +1,50 @@
1
+ require 'simple_pvr'
2
+
3
+ describe SimplePvr::PvrInitializer do
4
+ before do
5
+ SimplePvr::Model::DatabaseInitializer.stub(:setup)
6
+
7
+ @scheduler = double('Scheduler')
8
+ SimplePvr::Scheduler.stub(new: @scheduler)
9
+
10
+ @hdhomerun = double('HDHomeRun')
11
+ SimplePvr::HDHomeRun.stub(new: @hdhomerun)
12
+
13
+ @recording_manager = double('RecordingManager')
14
+ SimplePvr::RecordingManager.stub(new: @recording_manager)
15
+ end
16
+
17
+ it 'starts the scheduler' do
18
+ SimplePvr::Model::Channel.stub(all: [1, 2, 3, 4, 5])
19
+ @scheduler.should_receive(:start)
20
+
21
+ SimplePvr::PvrInitializer.setup
22
+ end
23
+
24
+ context 'when scheduler is started' do
25
+ before do
26
+ @scheduler.stub(:start)
27
+ end
28
+
29
+ it 'runs a channel scan if channels are missing' do
30
+ SimplePvr::Model::Channel.stub(all: [])
31
+ @hdhomerun.should_receive(:scan_for_channels)
32
+
33
+ SimplePvr::PvrInitializer.setup
34
+ end
35
+
36
+ it 'does nothing if channels.txt is present' do
37
+ SimplePvr::Model::Channel.stub(all: [1])
38
+
39
+ SimplePvr::PvrInitializer.setup
40
+ end
41
+
42
+ it 'initializes a HDHomeRun and RecordingManager instance' do
43
+ SimplePvr::Model::Channel.stub(all: [1])
44
+
45
+ SimplePvr::PvrInitializer.setup
46
+ SimplePvr::PvrInitializer.hdhomerun.should == @hdhomerun
47
+ SimplePvr::PvrInitializer.recording_manager.should == @recording_manager
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ require 'simple_pvr'
2
+
3
+ describe SimplePvr::Recorder do
4
+ before do
5
+ @channel = SimplePvr::Model::Channel.new(frequency: 282000000, channel_id: 1098)
6
+ @recording = SimplePvr::Model::Recording.new(@channel, 'Star Trek', 'start time', 'duration')
7
+
8
+ @hdhomerun = double('HDHomeRun')
9
+ SimplePvr::PvrInitializer.stub(hdhomerun: @hdhomerun)
10
+
11
+ @recording_manager = double('RecordingManager')
12
+ @recording_manager.stub(:create_directory_for_recording).with(@recording).and_return('recording directory')
13
+ SimplePvr::PvrInitializer.stub(recording_manager: @recording_manager)
14
+
15
+ @recorder = SimplePvr::Recorder.new(1, @recording)
16
+ end
17
+
18
+ it 'can start recording' do
19
+ @hdhomerun.should_receive(:start_recording).with(1, 282000000, 1098, 'recording directory')
20
+
21
+ @recorder.start!
22
+ end
23
+
24
+ it 'can stop recording as well, and creates a thumbnail' do
25
+ @hdhomerun.stub(:start_recording)
26
+ @hdhomerun.should_receive(:stop_recording).with(1)
27
+ SimplePvr::Ffmpeg.should_receive(:create_thumbnail_for).with('recording directory')
28
+
29
+ @recorder.start!
30
+ @recorder.stop!
31
+ end
32
+ end