simple_pvr 0.0.1

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.
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
+ });