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,158 @@
1
+ require 'simple_pvr'
2
+ require 'yaml'
3
+
4
+ describe SimplePvr::RecordingManager do
5
+ before do
6
+ @recording_dir = File.dirname(__FILE__) + '/../resources/recordings'
7
+ FileUtils.rm_rf(@recording_dir) if Dir.exists?(@recording_dir)
8
+ FileUtils.mkdir_p(@recording_dir + "/series 1/1")
9
+ FileUtils.mkdir_p(@recording_dir + "/series 1/3")
10
+ FileUtils.mkdir_p(@recording_dir + "/Another series/10")
11
+
12
+ @manager = SimplePvr::RecordingManager.new(@recording_dir)
13
+ end
14
+
15
+ it 'knows which shows are recorded' do
16
+ @manager.shows.should == ['Another series', 'series 1']
17
+ end
18
+
19
+ it 'can delete all episodes of a given show' do
20
+ @manager.delete_show('series 1')
21
+ File.exists?(@recording_dir + '/series 1').should be_false
22
+ end
23
+
24
+ it 'knows which episodes of a given show exists' do
25
+ episodes = @manager.episodes_of('series 1')
26
+
27
+ episodes.length.should == 2
28
+ episodes[0].episode.should == '1'
29
+ episodes[1].episode.should == '3'
30
+ end
31
+
32
+ it 'reads metadata for recordings if present' do
33
+ start_time = Time.now
34
+ metadata = {
35
+ channel: 'Channel 4',
36
+ subtitle: 'A subtitle',
37
+ description: 'A description',
38
+ start_time: start_time,
39
+ duration: 10.minutes
40
+ }
41
+ File.open(@recording_dir + '/series 1/3/metadata.yml', 'w') {|f| f.write(metadata.to_yaml) }
42
+
43
+ episodes = @manager.episodes_of('series 1')
44
+
45
+ episodes.length.should == 2
46
+
47
+ episodes[0].show_name.should == 'series 1'
48
+ episodes[0].episode.should == '1'
49
+
50
+ episodes[1].show_name.should == 'series 1'
51
+ episodes[1].episode.should == '3'
52
+ episodes[1].channel.should == 'Channel 4'
53
+ episodes[1].subtitle.should == 'A subtitle'
54
+ episodes[1].description.should == 'A description'
55
+ episodes[1].start_time.should == start_time
56
+ episodes[1].duration == 10.minutes
57
+ end
58
+
59
+ it 'knows when no thumbnail exists' do
60
+ episodes = @manager.episodes_of('series 1')
61
+
62
+ episodes[0].has_thumbnail.should == false
63
+ end
64
+
65
+ it 'knows when thumbnail exists' do
66
+ FileUtils.touch(@recording_dir + "/series 1/1/thumbnail.png")
67
+ episodes = @manager.episodes_of('series 1')
68
+
69
+ episodes[0].has_thumbnail.should == true
70
+ end
71
+
72
+ it 'knows when no webm file exists' do
73
+ episodes = @manager.episodes_of('series 1')
74
+
75
+ episodes[0].has_webm.should == false
76
+ end
77
+
78
+ it 'knows when a webm file exists' do
79
+ FileUtils.touch(@recording_dir + "/series 1/1/stream.webm")
80
+ episodes = @manager.episodes_of('series 1')
81
+
82
+ episodes[0].has_webm.should == true
83
+ end
84
+
85
+ it 'can delete an episode of a given show' do
86
+ @manager.delete_show_episode('series 1', '3')
87
+ File.exists?(@recording_dir + '/series 1/3').should be_false
88
+ end
89
+
90
+ context 'when creating recording directories' do
91
+ before do
92
+ @start_time = Time.local(2012, 7, 23, 15, 30, 15)
93
+ @recording = SimplePvr::Model::Recording.new(double(name: 'Channel 4'), 'Star Trek', @start_time, 50.minutes)
94
+ end
95
+
96
+ it 'records to directory with number 1 if nothing exists' do
97
+ @manager.create_directory_for_recording(@recording)
98
+
99
+ File.exists?(@recording_dir + '/Star Trek/1').should be_true
100
+ end
101
+
102
+ it 'removes some potentially harmful characters from directory name' do
103
+ @recording.show_name = "Some... harmful/irritating\\ characters in: '*title\""
104
+ @manager.create_directory_for_recording(@recording)
105
+
106
+ File.exists?(@recording_dir + '/Some harmfulirritating characters in title/1').should be_true
107
+ end
108
+
109
+ it 'finds a directory name for titles which would otherwise get an empty directory name' do
110
+ @recording.show_name = '/.'
111
+ @manager.create_directory_for_recording(@recording)
112
+
113
+ File.exists?(@recording_dir + '/Unnamed/1').should be_true
114
+ end
115
+
116
+ it 'finds next number in sequence for new directory' do
117
+ FileUtils.mkdir_p(@recording_dir + "/Star Trek/1")
118
+ FileUtils.mkdir_p(@recording_dir + "/Star Trek/2")
119
+ FileUtils.mkdir_p(@recording_dir + "/Star Trek/3")
120
+ @manager.create_directory_for_recording(@recording)
121
+
122
+ File.exists?(@recording_dir + '/Star Trek/4').should be_true
123
+ end
124
+
125
+ it 'ignores random directories which are not sequence numbers' do
126
+ FileUtils.mkdir_p(@recording_dir + "/Star Trek/4")
127
+ FileUtils.mkdir_p(@recording_dir + "/Star Trek/random directory name")
128
+ @manager.create_directory_for_recording(@recording)
129
+
130
+ File.exists?(@recording_dir + '/Star Trek/5').should be_true
131
+ end
132
+
133
+ it 'stores simple metadata if no programme information exists' do
134
+ @manager.create_directory_for_recording(@recording)
135
+
136
+ metadata = YAML.load_file(@recording_dir + '/Star Trek/1/metadata.yml')
137
+ metadata[:title].should == 'Star Trek'
138
+ metadata[:channel].should == 'Channel 4'
139
+ metadata[:start_time].should == @start_time
140
+ metadata[:duration].should == 50.minutes
141
+ end
142
+
143
+ it 'stores extensive metadata if programme information exists' do
144
+ start_time = Time.local(2012, 7, 23, 15, 30, 15)
145
+ recording = SimplePvr::Model::Recording.new(double(name: 'Channel 4'), 'Extensive Metadata', start_time, 50.minutes)
146
+ recording.programme = SimplePvr::Model::Programme.new(subtitle: 'A subtitle', description: "A description,\nspanning several lines")
147
+ @manager.create_directory_for_recording(recording)
148
+
149
+ metadata = YAML.load_file(@recording_dir + '/Extensive Metadata/1/metadata.yml')
150
+ metadata[:title].should == 'Extensive Metadata'
151
+ metadata[:channel].should == 'Channel 4'
152
+ metadata[:start_time].should == start_time
153
+ metadata[:duration].should == 50.minutes
154
+ metadata[:subtitle].should == 'A subtitle'
155
+ metadata[:description].should == "A description,\nspanning several lines"
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,104 @@
1
+ require 'simple_pvr'
2
+
3
+ describe SimplePvr::RecordingPlanner do
4
+ before do
5
+ SimplePvr::Model::DatabaseInitializer.prepare_for_test
6
+ SimplePvr::Model::DatabaseInitializer.clear
7
+ @dr_1 = SimplePvr::Model::Channel.create(name: 'DR 1')
8
+ @dr_k = SimplePvr::Model::Channel.create(name: 'DR K')
9
+ @start_time_1, @start_time_2 = Time.local(2012, 7, 10, 20, 50), Time.local(2012, 7, 17, 20, 50)
10
+ @programme_title = 'Borgias'
11
+ @programme_duration = 60.minutes.to_i
12
+
13
+ @scheduler = double('Scheduler')
14
+ SimplePvr::PvrInitializer.stub(scheduler: @scheduler)
15
+ end
16
+
17
+ it 'resets the recordings when no schedules exist' do
18
+ SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_k, start_time: @start_time_1, duration: @programme_duration)
19
+ SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_k, start_time: @start_time_2, duration: @programme_duration)
20
+ SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_1, start_time: @start_time_2, duration: @programme_duration)
21
+ SimplePvr::Model::Programme.create(title: @programme_title + "-", channel: @dr_k, start_time: @start_time_2, duration: @programme_duration)
22
+
23
+ @scheduler.should_receive(:recordings=).with([])
24
+
25
+ SimplePvr::RecordingPlanner.read
26
+ end
27
+
28
+ it 'can set up schedules from channel and programme title' do
29
+ SimplePvr::Model::Schedule.create(type: :specification, title: 'Borgias', channel: @dr_k)
30
+ @programme_1 = SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_k, start_time: @start_time_1, duration: @programme_duration)
31
+ @programme_2 = SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_k, start_time: @start_time_2, duration: @programme_duration)
32
+ SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_1, start_time: @start_time_2, duration: @programme_duration)
33
+ SimplePvr::Model::Programme.create(title: @programme_title + "-", channel: @dr_k, start_time: @start_time_2, duration: @programme_duration)
34
+
35
+ @scheduler.should_receive(:recordings=).with([
36
+ SimplePvr::Model::Recording.new(@dr_k, 'Borgias', Time.local(2012, 7, 10, 20, 48), 67.minutes, @programme_1),
37
+ SimplePvr::Model::Recording.new(@dr_k, 'Borgias', Time.local(2012, 7, 17, 20, 48), 67.minutes, @programme_2)
38
+ ])
39
+
40
+ SimplePvr::RecordingPlanner.read
41
+ end
42
+
43
+ it 'can set up schedules from channel, programme title, and start time' do
44
+ SimplePvr::Model::Schedule.create(type: :specification, title: 'Borgias', channel: @dr_k, start_time: @start_time_2)
45
+ SimplePvr::Model::Programme.create(title: @programme_title, channel:@dr_k, start_time: @start_time_1, duration: @programme_duration)
46
+ @programme_to_be_recorded = SimplePvr::Model::Programme.create(title: @programme_title, channel:@dr_k, start_time: @start_time_2, duration: @programme_duration)
47
+
48
+ @scheduler.should_receive(:recordings=).with([
49
+ SimplePvr::Model::Recording.new(@dr_k, @programme_title, @start_time_2.advance(minutes: -2), 2.minutes + @programme_duration + 5.minutes, @programme_to_be_recorded)
50
+ ])
51
+
52
+ SimplePvr::RecordingPlanner.read
53
+ end
54
+
55
+ it 'can set up schedules from programme title only' do
56
+ SimplePvr::Model::Schedule.create(type: :specification, title: 'Borgias')
57
+ @programme_1 = SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_1, start_time: @start_time_1, duration: @programme_duration)
58
+ @programme_2 = SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_k, start_time: @start_time_2, duration: @programme_duration)
59
+ SimplePvr::Model::Programme.create(title: @programme_title + '-', channel: @dr_k, start_time: @start_time_2, duration: @programme_duration)
60
+
61
+ @scheduler.should_receive(:recordings=).with([
62
+ SimplePvr::Model::Recording.new(@dr_1, 'Borgias', Time.local(2012, 7, 10, 20, 48), 67.minutes, @programme_1),
63
+ SimplePvr::Model::Recording.new(@dr_k, 'Borgias', Time.local(2012, 7, 17, 20, 48), 67.minutes, @programme_2)
64
+ ])
65
+
66
+ SimplePvr::RecordingPlanner.read
67
+ end
68
+
69
+ it 'can set up schedules for specific days of the week' do
70
+ @monday = Time.local(2012, 12, 10, 20, 50)
71
+ @tuesday, @wednesday, @thursday, @friday, @saturday, @sunday = @monday + 1.day, @monday + 2.days, @monday + 3.days, @monday + 4.days, @monday + 5.days, @monday + 6.days
72
+ @monday_programme = SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_1, start_time: @monday, duration: @programme_duration)
73
+ @tuesday_programme = SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_1, start_time: @tuesday, duration: @programme_duration)
74
+ @wednesday_programme = SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_1, start_time: @wednesday, duration: @programme_duration)
75
+ @thursday_programme = SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_1, start_time: @thursday, duration: @programme_duration)
76
+ @friday_programme = SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_1, start_time: @friday, duration: @programme_duration)
77
+ @saturday_programme = SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_1, start_time: @saturday, duration: @programme_duration)
78
+ @sunday_programme = SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_1, start_time: @sunday, duration: @programme_duration)
79
+
80
+ SimplePvr::Model::Schedule.create(type: :specification, title: @programme_title, filter_by_weekday: true, monday: false, tuesday: true, wednesday: true, thursday: false, friday: false, saturday: true, sunday: true)
81
+
82
+ @scheduler.should_receive(:recordings=).with([
83
+ SimplePvr::Model::Recording.new(@dr_1, @programme_title, @tuesday - 2.minutes, 67.minutes, @tuesday_programme),
84
+ SimplePvr::Model::Recording.new(@dr_1, @programme_title, @wednesday - 2.minutes, 67.minutes, @wednesday_programme),
85
+ SimplePvr::Model::Recording.new(@dr_1, @programme_title, @saturday - 2.minutes, 67.minutes, @saturday_programme),
86
+ SimplePvr::Model::Recording.new(@dr_1, @programme_title, @sunday - 2.minutes, 67.minutes, @sunday_programme),
87
+ ])
88
+
89
+ SimplePvr::RecordingPlanner.read
90
+ end
91
+
92
+ it 'ignores programmes for which exceptions exist' do
93
+ SimplePvr::Model::Schedule.create(type: :specification, title: 'Borgias')
94
+ SimplePvr::Model::Schedule.create(type: :exception, title: 'Borgias', channel: @dr_1, start_time: @start_time_1)
95
+ SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_1, start_time: @start_time_1, duration: @programme_duration)
96
+ @programme_to_be_recorded = SimplePvr::Model::Programme.create(title: @programme_title, channel: @dr_k, start_time: @start_time_2, duration: @programme_duration)
97
+
98
+ @scheduler.should_receive(:recordings=).with([
99
+ SimplePvr::Model::Recording.new(@dr_k, 'Borgias', Time.local(2012, 7, 17, 20, 48), 67.minutes, @programme_to_be_recorded)
100
+ ])
101
+
102
+ SimplePvr::RecordingPlanner.read
103
+ end
104
+ end
@@ -0,0 +1,201 @@
1
+ require 'simple_pvr'
2
+
3
+ describe SimplePvr::Scheduler do
4
+ before do
5
+ @channel = double('Channel DR K', name: 'DR K', frequency: 282000000, channel_id: 1098)
6
+ @channel_dr1 = double('Channel DR 1', name: 'DR 1', frequency: 290000000, channel_id: 1099)
7
+
8
+ @scheduler = SimplePvr::Scheduler.new
9
+ end
10
+
11
+ it 'leaves recordings that are in the future' do
12
+ start_time = Time.local(2012, 7, 15, 20, 15, 30)
13
+ Time.stub(:now => start_time.advance(hours: -1))
14
+
15
+ @scheduler.recordings = [SimplePvr::Model::Recording.new(@channel, 'Borgia', start_time, 60.minutes)]
16
+ @scheduler.process
17
+ end
18
+
19
+ it 'marks conflicting future recordings' do
20
+ first_start_time = Time.now.advance(days: 1)
21
+ second_start_time = Time.now.advance(days: 1, minutes: 10)
22
+ third_start_time = Time.now.advance(days: 1, minutes: 20)
23
+ fourth_start_time = Time.now.advance(days: 2)
24
+ first_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', first_start_time, 60.minutes)
25
+ second_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', second_start_time, 60.minutes)
26
+ third_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', third_start_time, 60.minutes)
27
+ fourth_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', fourth_start_time, 60.minutes)
28
+
29
+ @scheduler.recordings = [first_recording, third_recording, second_recording]
30
+
31
+ first_recording.should_not be_conflicting
32
+ second_recording.should_not be_conflicting
33
+ third_recording.should be_conflicting
34
+ fourth_recording.should_not be_conflicting
35
+ end
36
+
37
+ it 'starts recordings at start time' do
38
+ start_time = Time.local(2012, 7, 15, 20, 15, 30)
39
+ starting_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', start_time, 60.minutes)
40
+ Time.stub(:now => start_time)
41
+ SimplePvr::Recorder.stub(:new).with(0, starting_recording).and_return(@recorder = double('Recorder'))
42
+ @recorder.should_receive(:start!)
43
+
44
+ @scheduler.recordings = [starting_recording]
45
+ @scheduler.process
46
+ end
47
+
48
+ it 'starts recordings that are in progress' do
49
+ start_time = Time.local(2012, 7, 15, 20, 15, 30)
50
+ recording_in_progress = SimplePvr::Model::Recording.new(@channel, 'Borgia', start_time, 60.minutes)
51
+ Time.stub(:now => start_time.advance(minutes: 30))
52
+ SimplePvr::Recorder.stub(:new).with(0, recording_in_progress).and_return(@recorder = double('Recorder'))
53
+ @recorder.should_receive(:start!)
54
+
55
+ @scheduler.recordings = [recording_in_progress]
56
+ @scheduler.process
57
+ end
58
+
59
+ it 'can start two recordings at once' do
60
+ first_start_time = Time.local(2012, 7, 15, 19, 45, 30)
61
+ second_start_time = Time.local(2012, 7, 15, 20, 15, 30)
62
+ recording_in_progress = SimplePvr::Model::Recording.new(@channel, 'Borgia', first_start_time, 60.minutes)
63
+ starting_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', second_start_time, 60.minutes)
64
+ Time.stub(:now => second_start_time)
65
+ SimplePvr::Recorder.stub(:new).with(0, recording_in_progress).and_return(@recorder0 = double('Recorder'))
66
+ SimplePvr::Recorder.stub(:new).with(1, starting_recording).and_return(@recorder1 = double('Recorder'))
67
+ @recorder0.should_receive(:start!)
68
+ @recorder1.should_receive(:start!)
69
+
70
+ @scheduler.recordings = [recording_in_progress, starting_recording]
71
+ @scheduler.process
72
+ end
73
+
74
+ it 'rejects third recording at once, and marks the third recording as conflicting' do
75
+ first_start_time = Time.local(2012, 7, 15, 19, 45, 30)
76
+ second_start_time = Time.local(2012, 7, 15, 20, 15, 30)
77
+ third_start_time = Time.local(2012, 7, 15, 20, 20, 0)
78
+ recording_in_progress = SimplePvr::Model::Recording.new(@channel, 'Borgia', first_start_time, 60.minutes)
79
+ starting_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', second_start_time, 60.minutes)
80
+ rejected_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', third_start_time, 60.minutes)
81
+ Time.stub(:now => second_start_time)
82
+ SimplePvr::Recorder.stub(:new).with(0, recording_in_progress).and_return(@recorder0 = double('Recorder'))
83
+ SimplePvr::Recorder.stub(:new).with(1, starting_recording).and_return(@recorder1 = double('Recorder'))
84
+ @recorder0.should_receive(:start!)
85
+ @recorder1.should_receive(:start!)
86
+
87
+ @scheduler.recordings = [recording_in_progress, starting_recording, rejected_recording]
88
+ @scheduler.process
89
+
90
+ recording_in_progress.should_not be_conflicting
91
+ starting_recording.should_not be_conflicting
92
+ rejected_recording.should be_conflicting
93
+ end
94
+
95
+ it 'ends recordings at end time' do
96
+ start_time = Time.local(2012, 7, 15, 20, 15, 30)
97
+ ending_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', start_time, 60.minutes)
98
+ SimplePvr::Recorder.stub(:new).with(0, ending_recording).and_return(@recorder = double('Recorder'))
99
+ @recorder.should_receive(:start!)
100
+ Time.stub(:now => start_time)
101
+
102
+ @scheduler.recordings = [ending_recording]
103
+ @scheduler.process
104
+
105
+ @recorder.should_receive(:stop!)
106
+ Time.stub(:now => start_time.advance(hours: 1, minutes: 1))
107
+
108
+ @scheduler.process
109
+ end
110
+
111
+ it 'skips recordings that have passed' do
112
+ start_time = Time.local(2012, 7, 15, 20, 15, 30)
113
+ passed_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', start_time - 65.minutes, 60.minutes)
114
+ Time.stub(:now => start_time)
115
+
116
+ @scheduler.recordings = [passed_recording]
117
+ @scheduler.process
118
+ end
119
+
120
+ it 'knows which programmes are being recorded' do
121
+ start_time = Time.local(2012, 7, 15, 20, 15, 30)
122
+ Time.stub(:now => start_time.advance(hours: -1))
123
+ scheduled_programme = double(id: 2)
124
+ unscheduled_programme = double(id: 3)
125
+
126
+ @scheduler.recordings = [SimplePvr::Model::Recording.new(@channel, 'Borgia', start_time, 60.minutes, scheduled_programme)]
127
+ @scheduler.is_scheduled?(scheduled_programme).should be_true
128
+ @scheduler.is_scheduled?(unscheduled_programme).should be_false
129
+ end
130
+
131
+ it 'gives idle status when nothing is recording' do
132
+ @scheduler.recordings = []
133
+ @scheduler.process
134
+
135
+ @scheduler.status_text.should == 'Idle'
136
+ end
137
+
138
+ it 'gives recording status when recording' do
139
+ start_time = Time.local(2012, 7, 15, 20, 15, 30)
140
+ Time.stub(:now => start_time.advance(minutes: 30))
141
+ SimplePvr::Recorder.stub(new: (@recorder = double('Recorder')))
142
+ @recorder.stub(:start!)
143
+
144
+ @scheduler.recordings = [SimplePvr::Model::Recording.new(@channel, 'Borgia', start_time, 60.minutes)]
145
+ @scheduler.process
146
+
147
+ @scheduler.status_text.should == "Recording 'Borgia' on channel 'DR K'"
148
+ end
149
+
150
+ context 'when updating existing recordings' do
151
+ it 'leaves running recording if new recording is equal' do
152
+ start_time = Time.local(2012, 7, 15, 20, 15, 30)
153
+ continuing_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', start_time, 60.minutes)
154
+ Time.stub(:now => start_time)
155
+ SimplePvr::Recorder.stub(:new).with(0, continuing_recording).and_return(@recorder = double('Recorder'))
156
+ @recorder.should_receive(:start!)
157
+
158
+ @scheduler.recordings = [continuing_recording]
159
+ @scheduler.process
160
+
161
+ @scheduler.recordings = [SimplePvr::Model::Recording.new(@channel, 'Borgia', start_time, 60.minutes)]
162
+ @scheduler.process
163
+ end
164
+
165
+ it 'stops existing recording if not present in new recording list' do
166
+ start_time = Time.local(2012, 7, 15, 20, 15, 30)
167
+ stopping_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', start_time, 60.minutes)
168
+ upcoming_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', start_time + 10.minutes, 60.minutes)
169
+ Time.stub(:now => start_time)
170
+ SimplePvr::Recorder.stub(:new).with(0, stopping_recording).and_return(@recorder = double('Recorder'))
171
+ @recorder.should_receive(:start!)
172
+
173
+ @scheduler.recordings = [stopping_recording]
174
+ @scheduler.process
175
+
176
+ @recorder.should_receive(:stop!)
177
+
178
+ @scheduler.recordings = [upcoming_recording]
179
+ @scheduler.process
180
+ end
181
+
182
+ it 'stops existing recording and starts new recording if new recording list has other current recording' do
183
+ start_time = Time.local(2012, 7, 15, 20, 15, 30)
184
+ stopping_recording = SimplePvr::Model::Recording.new(@channel, 'Borgia', start_time, 60.minutes)
185
+ starting_recording = SimplePvr::Model::Recording.new(@channel_dr1, 'Sports', start_time, 60.minutes)
186
+ Time.stub(:now => start_time)
187
+ SimplePvr::Recorder.stub(:new).with(0, stopping_recording).and_return(@old_recorder = double('Recorder'))
188
+ SimplePvr::Recorder.stub(:new).with(0, starting_recording).and_return(@new_recorder = double('New recorder'))
189
+ @old_recorder.should_receive(:start!)
190
+
191
+ @scheduler.recordings = [stopping_recording]
192
+ @scheduler.process
193
+
194
+ @old_recorder.should_receive(:stop!)
195
+ @new_recorder.should_receive(:start!)
196
+
197
+ @scheduler.recordings = [starting_recording]
198
+ @scheduler.process
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,49 @@
1
+ #encoding: UTF-8
2
+ require 'simple_pvr'
3
+
4
+ describe SimplePvr::XmltvReader do
5
+ before do
6
+ @dr_1 = double(name: 'DR 1')
7
+ @dr_1.stub(:icon_url=)
8
+
9
+ SimplePvr::Model::Channel.stub(all: [@dr_1])
10
+ SimplePvr::Model::Programme.stub(:transaction).and_yield
11
+ SimplePvr::Model::Programme.stub(:destroy)
12
+ @xmltv_reader = SimplePvr::XmltvReader.new({'www.ontv.dk/tv/1' => 'DR 1'})
13
+ end
14
+
15
+ it 'populates programme information through the DAO' do
16
+ SimplePvr::Model::Programme.stub(:add)
17
+ SimplePvr::Model::Programme.should_receive(:add).with(
18
+ @dr_1,
19
+ 'Noddy',
20
+ 'Bare vær dig selv, Noddy.',
21
+ "Tegnefilm.\nHer 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!",
22
+ Time.new(2012, 7, 17, 6, 0, 0, "+02:00"),
23
+ 10.minutes,
24
+ ' .2/12. ')
25
+
26
+ @xmltv_reader.read(File.new(File.dirname(__FILE__) + '/../resources/programs.xmltv'))
27
+ end
28
+
29
+ it 'ignores programmes for channels with no mapping' do
30
+ # There are two channels in the XMLTV file, but only one with a mapping
31
+ SimplePvr::Model::Programme.should_receive(:add).exactly(5).times
32
+
33
+ @xmltv_reader.read(File.new(File.dirname(__FILE__) + '/../resources/programs.xmltv'))
34
+ end
35
+
36
+ it 'adds channel icons to existing channels' do
37
+ SimplePvr::Model::Programme.stub(:add) # necessary to deal with the way Programme setup its relation to Channel
38
+ @dr_1.should_receive(:icon_url=).with("http://ontv.dk/imgs/epg/logos/dr1_big.png")
39
+
40
+ @xmltv_reader.read(File.new(File.dirname(__FILE__) + '/../resources/programs.xmltv'))
41
+ end
42
+
43
+ it 'has no problem with xml with no channel icons' do
44
+ SimplePvr::Model::Programme.stub(:add) # necessary to deal with the way Programme setup its relation to Channel
45
+ @dr_1.should_not_receive(:icon_url=)
46
+
47
+ @xmltv_reader.read(File.new(File.dirname(__FILE__) + '/../resources/programs-without-icon.xmltv'))
48
+ end
49
+ end
@@ -0,0 +1,10 @@
1
+ server: http://localhost:9877
2
+
3
+ load:
4
+ - test/lib/angular/angular-scenario.js
5
+ - test/config/jstd-scenario-adapter-config.js
6
+ - test/lib/angular/jstd-scenario-adapter.js
7
+ - test/e2e/scenarios.js
8
+
9
+ proxy:
10
+ - {matcher: "*", server: "http://localhost:8000"}
@@ -0,0 +1,12 @@
1
+ server: http://localhost:9876
2
+
3
+ load:
4
+ - test/lib/jasmine/jasmine.js
5
+ - test/lib/jasmine-jstd-adapter/JasmineAdapter.js
6
+ - public/js/angular/angular.min.js
7
+ - public/js/angular/angular-resource.min.js
8
+ - test/lib/angular/angular-mocks.js
9
+ - public/js/*.js
10
+ - test/*.js
11
+
12
+ exclude:
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Configuration for jstd scenario adapter
3
+ */
4
+ var jstdScenarioAdapter = {
5
+ relativeUrlPrefix: '/test/e2e/'
6
+ };
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ describe('filters', function() {
4
+ beforeEach(module('simplePvr'));
5
+
6
+ describe('chunk', function() {
7
+ it('should let non-Array input pass through', inject(function(chunkFilter) {
8
+ var input = 'this is not an array';
9
+ expect(chunkFilter(input)).toBe(input);
10
+ }));
11
+
12
+ it('should let empty arrays pass through', inject(function(chunkFilter) {
13
+ expect(chunkFilter([], 3)).toEqual([]);
14
+ }));
15
+
16
+ it('should chunk up arrays and give them hashKeys corresponding to the chunk numbers', inject(function(chunkFilter) {
17
+ var input = [1, 2, 3, 4, 5, 6, 7];
18
+ var firstChunk = [1, 2, 3];
19
+ var secondChunk = [4, 5, 6];
20
+ var thirdChunk = [7];
21
+
22
+ firstChunk.$$hashKey = 0;
23
+ secondChunk.$$hashKey = 1;
24
+ thirdChunk.$$hashKey = 2;
25
+
26
+ expect(chunkFilter(input, 3)).toEqual([firstChunk, secondChunk, thirdChunk]);
27
+ }));
28
+
29
+ it('should create equal outputs given equal inputs', inject(function(chunkFilter) {
30
+ var output1 = chunkFilter([1, 2, 3, 4], 3);
31
+ var output2 = chunkFilter([1, 2, 3, 4], 3);
32
+ expect(angular.equals(output1, output2)).toBeTruthy();
33
+ }));
34
+
35
+ it('should create non-equal outputs given non-equal inputs', inject(function(chunkFilter) {
36
+ var output1 = chunkFilter([1, 2, 3, 4], 3);
37
+ var output2 = chunkFilter([4, 3, 2, 1], 3);
38
+ expect(angular.equals(output1, output2)).toBeFalsy();
39
+ }));
40
+ });
41
+
42
+ describe('weekdayFilter', function() {
43
+ it('should be blank when there is no weekday filtering', inject(function(filteredWeekdaysFilter) {
44
+ var scheduleWithNoWeekdayFiltering = { filter_by_weekday: false };
45
+ expect(filteredWeekdaysFilter(scheduleWithNoWeekdayFiltering)).toEqual('');
46
+ }));
47
+
48
+ it('should name a single weekday if only one is selected', inject(function(filteredWeekdaysFilter) {
49
+ var scheduleWithOneWeekdayFiltering = {
50
+ filter_by_weekday: true,
51
+ monday: false,
52
+ tuesday: false,
53
+ wednesday: true,
54
+ thursday: false,
55
+ friday: false,
56
+ saturday: false,
57
+ sunday: false
58
+ };
59
+ expect(filteredWeekdaysFilter(scheduleWithOneWeekdayFiltering)).toEqual('(Wednesdays)');
60
+ }));
61
+
62
+ it('should name two weekdays if exactly two are selected', inject(function(filteredWeekdaysFilter) {
63
+ var scheduleWithTwoWeekdayFilterings = {
64
+ filter_by_weekday: true,
65
+ monday: true,
66
+ tuesday: false,
67
+ wednesday: true,
68
+ thursday: false,
69
+ friday: false,
70
+ saturday: false,
71
+ sunday: false
72
+ };
73
+ expect(filteredWeekdaysFilter(scheduleWithTwoWeekdayFilterings)).toEqual('(Mondays and Wednesdays)');
74
+ }));
75
+
76
+ it('should list all weekdays for three and more allowed weekdays', inject(function(filteredWeekdaysFilter) {
77
+ var scheduleWithSeveralWeekdayFilterings = {
78
+ filter_by_weekday: true,
79
+ monday: true,
80
+ tuesday: false,
81
+ wednesday: true,
82
+ thursday: false,
83
+ friday: true,
84
+ saturday: false,
85
+ sunday: false
86
+ };
87
+ expect(filteredWeekdaysFilter(scheduleWithSeveralWeekdayFilterings)).toEqual('(Mondays, Wednesdays, and Fridays)');
88
+ scheduleWithSeveralWeekdayFilterings.saturday = true;
89
+ expect(filteredWeekdaysFilter(scheduleWithSeveralWeekdayFilterings)).toEqual('(Mondays, Wednesdays, Fridays, and Saturdays)');
90
+ scheduleWithSeveralWeekdayFilterings.tuesday = true;
91
+ scheduleWithSeveralWeekdayFilterings.thursday = true;
92
+ scheduleWithSeveralWeekdayFilterings.saturday = true;
93
+ scheduleWithSeveralWeekdayFilterings.sunday = true;
94
+ expect(filteredWeekdaysFilter(scheduleWithSeveralWeekdayFilterings)).toEqual('(Mondays, Tuesdays, Wednesdays, Thursdays, Fridays, Saturdays, and Sundays)')
95
+ }));
96
+ });
97
+ });