├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── Makefile ├── README.md ├── Rakefile ├── docs └── logo.png ├── house.rb.gemspec ├── lib ├── house.rb └── house │ ├── episode.rb │ ├── podcast.rb │ └── version.rb └── spec ├── lib └── house │ └── podcast_spec.rb ├── spec_helper.rb └── test_data ├── example1.jpg ├── example1.m4a ├── example2.jpg ├── example2.m4a ├── invalid.xml ├── valid.xml ├── valid_future.xml ├── valid_past.xml ├── valid_with_2_items.xml ├── valid_with_items_without_itunes.xml ├── valid_with_itunes.xml ├── valid_without_items.xml └── valid_without_itunes.xml /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | html/ 3 | pkg/ 4 | vendor/cache/*.gem 5 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color --format documentation 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.4.1 4 | - ruby-head 5 | 6 | matrix: 7 | allow_failures: 8 | - rvm: ruby-head 9 | 10 | before_install: 11 | - gem install bundler -v 1.13.7 12 | 13 | install: 14 | - make 15 | 16 | script: 17 | - make test 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2017 Marcelo Boeira 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default 2 | default: setup 3 | 4 | .PHONY: setup 5 | setup: 6 | bundle install 7 | 8 | .PHONY: spec 9 | spec: 10 | bundle exec rspec 11 | 12 | .PHONY: test 13 | test: spec 14 | 15 | .PHONY: install 16 | install: 17 | bundle exec rake install 18 | 19 | .PHONY: release 20 | release: 21 | bundle exec rake release 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
:pill: The Podcast RSS Reader and Diagnostician
5 |
6 | 7 | > :round_pushpin: This is a refactor/fork of [kule/podcast_reader](https://github.com/kule/podcast_reader), thanks [@kule](https://github.com/kule) for the efforts so far! 8 | 9 | ## Installation 10 | 11 | Add this line to your application's Gemfile: 12 | 13 | ```ruby 14 | gem "house.rb" 15 | ``` 16 | 17 | And then execute: 18 | 19 | ```bash 20 | $ bundle 21 | ``` 22 | 23 | Or install it yourself as: 24 | 25 | ```bash 26 | $ gem install house.rb 27 | ``` 28 | 29 | ## Usage 30 | 31 | ```ruby 32 | require "house.rb" 33 | 34 | podcast = House::Podcast.new("http://feeds.feedburner.com/developertea") 35 | 36 | pocast.title 37 | pocast.description 38 | pocast.title 39 | pocast.description 40 | pocast.language 41 | pocast.last_build_date 42 | pocast.site_link 43 | 44 | podcast.image_url 45 | podcast.image_title 46 | podcast.image_link 47 | 48 | podcast.author 49 | podcast.subtitle 50 | podcast.summary 51 | 52 | podcast.publish_date 53 | 54 | podcast.keywords 55 | podcast.explicit? 56 | podcast.download_image_to("./podcast_image.jpg") 57 | podcast.updated_since(Time.now) 58 | podcast.items.size 59 | 60 | episode = podcast.items.first 61 | 62 | episode.title 63 | episode.description 64 | episode.category 65 | episode.author 66 | episode.subtitle 67 | episode.summary 68 | episode.duration 69 | episode.url 70 | episode.content_type 71 | episode.file_size 72 | 73 | episodes.download_to("./podcast_track.mp3") 74 | ``` 75 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | task :default => :spec 3 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feedcast/house/f6c064064427fe50b0ed765e0500841861e3663c/docs/logo.png -------------------------------------------------------------------------------- /house.rb.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path("../lib", __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require "house/version" 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "house.rb" 8 | gem.version = House::VERSION 9 | gem.summary = %q{The Podcast RSS Reader and Diagnostician} 10 | gem.description = %q{The Podcast RSS Reader and Diagnostician} 11 | gem.license = "Apache-2.0" 12 | gem.authors = ["Marcelo Boeira"] 13 | gem.email = "me@marceloboeira.com" 14 | gem.homepage = "https://github.com/feedcast/house" 15 | 16 | gem.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|features)/}) } 17 | gem.require_paths = ["lib"] 18 | 19 | gem.add_dependency "nokogiri", "~> 1" 20 | gem.add_dependency "open_uri_redirections", "~> 0" 21 | 22 | gem.add_development_dependency "bundler", "~> 1.0" 23 | gem.add_development_dependency "rake", "~> 0.9.2" 24 | gem.add_development_dependency "rspec", "~> 2.4" 25 | gem.add_development_dependency "sham_rack", "~> 1.3" 26 | gem.add_development_dependency "sinatra", "~> 1" 27 | gem.add_development_dependency "byebug", "~> 9" 28 | end 29 | -------------------------------------------------------------------------------- /lib/house.rb: -------------------------------------------------------------------------------- 1 | require "house/version" 2 | require "house/podcast" 3 | require "house/episode" 4 | 5 | module House; end 6 | -------------------------------------------------------------------------------- /lib/house/episode.rb: -------------------------------------------------------------------------------- 1 | require "nokogiri" 2 | require "open-uri" 3 | 4 | module House 5 | class Episode 6 | def initialize(item_node) 7 | @item_xml = item_node 8 | end 9 | 10 | def title; item_attr("title"); end 11 | def description; item_attr("description"); end 12 | def category; item_attr("category"); end 13 | 14 | def author; item_attr("itunes:author"); end 15 | def subtitle; item_attr("itunes:subtitle"); end 16 | def summary; item_attr("itunes:summary"); end 17 | def duration; item_attr("itunes:duration"); end 18 | 19 | def url; item_attr("enclosure", "url"); end 20 | def content_type; item_attr("enclosure", "size"); end 21 | def file_size; item_attr("enclosure", "length"); end 22 | 23 | 24 | def keywords 25 | item_attr("itunes:keywords").to_s.split(",") 26 | end 27 | 28 | def explicit? 29 | explicit = item_attr("itunes:explicit") 30 | case explicit 31 | when "yes" then true 32 | when "no" then false 33 | else 34 | nil 35 | end 36 | end 37 | 38 | def block? 39 | block = item_attr("itunes:block") 40 | case block 41 | when "yes" then true 42 | when "no" then false 43 | else 44 | nil 45 | end 46 | end 47 | 48 | def publish_date 49 | Time.parse(item_attr("pubDate")) if item_attr("pubDate") 50 | end 51 | 52 | def download_to(file_path) 53 | dir_path = File.dirname(file_path) 54 | FileUtils.mkdir_p(dir_path) unless File.exist?(dir_path) 55 | File.open(file_path, "w") do |f| 56 | f.write open(self.url).read 57 | end 58 | end 59 | 60 | private 61 | 62 | def item_attr(xpath, attribute = nil) 63 | node = @item_xml.xpath(xpath) 64 | if node.nil? || node.empty? 65 | nil 66 | else 67 | if attribute.nil? 68 | node.text 69 | elsif node.attr(attribute) 70 | node.attr(attribute).value 71 | end 72 | end 73 | rescue Nokogiri::XML::XPath::SyntaxError => e 74 | nil 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/house/podcast.rb: -------------------------------------------------------------------------------- 1 | require "nokogiri" 2 | require "open-uri" 3 | require "open_uri_redirections" 4 | require "house/episode" 5 | 6 | module House 7 | class Podcast 8 | def initialize(url) 9 | @url = url 10 | @podcast = Nokogiri::XML(open(url, allow_redirections: :all)) 11 | @items = [] 12 | raise "The supplied url is not a valid podcast rss" if @podcast.at_xpath("/rss").nil? 13 | end 14 | 15 | def title; attr("/rss/channel/title"); end 16 | def description; attr("/rss/channel/description"); end 17 | def language; attr("/rss/channel/language"); end 18 | def last_build_date; attr("/rss/channel/lastBuildDate"); end 19 | def site_link; attr("/rss/channel/link"); end 20 | 21 | def image_url; attr("/rss/channel/image/url"); end 22 | def image_title; attr("/rss/channel/image/title"); end 23 | def image_link; attr("/rss/channel/image/link"); end 24 | 25 | def author; attr("/rss/channel/itunes:author"); end 26 | def subtitle; attr("/rss/channel/itunes:subtitle"); end 27 | def summary; attr("/rss/channel/itunes:summary"); end 28 | 29 | def keywords 30 | attr("/rss/channel/itunes:keywords").to_s.split(",") 31 | end 32 | 33 | def explicit? 34 | explicit = attr("/rss/channel/itunes:explicit") 35 | case explicit 36 | when "yes" then true 37 | when "no" then false 38 | else 39 | nil 40 | end 41 | end 42 | 43 | def publish_date 44 | pub_date = attr("/rss/channel/pubDate") || attr("/rss/channel/item[1]/pubDate") 45 | Time.parse(pub_date) 46 | end 47 | 48 | def items 49 | if @items.empty? && @podcast.xpath("/rss/channel/item") 50 | @podcast.xpath("/rss/channel/item").each do |node| 51 | @items << House::Episode.new(node) 52 | end 53 | end 54 | @items 55 | end 56 | 57 | def download_image_to(file_path) 58 | dir_path = File.dirname(file_path) 59 | FileUtils.mkdir_p(dir_path) unless File.exist?(dir_path) 60 | File.open(file_path, "w") do |f| 61 | f.write open(self.image_url).read 62 | end 63 | end 64 | 65 | def updated_since?(time) 66 | publish_date > time 67 | end 68 | 69 | private 70 | 71 | def attr(xpath) 72 | node = @podcast.xpath(xpath) 73 | if node.nil? || node.empty? 74 | nil 75 | else 76 | node.text 77 | end 78 | rescue Nokogiri::XML::XPath::SyntaxError => e 79 | nil 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/house/version.rb: -------------------------------------------------------------------------------- 1 | module House 2 | VERSION = "0.1.1" 3 | end 4 | -------------------------------------------------------------------------------- /spec/lib/house/podcast_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe House::Podcast do 4 | it "should accept a valid podcast url" do 5 | lambda { House::Podcast.new("http://podcasts.example.com/valid.xml") }.should_not raise_error 6 | end 7 | 8 | context "when the feed link redirects" do 9 | it "should not raise an error" do 10 | lambda { House::Podcast.new("http://podcasts.example.com/redirect.xml") }.should_not raise_error 11 | end 12 | end 13 | 14 | it "should raise exception when given an invalid podcast url" do 15 | lambda { House::Podcast.new("http://podcasts.example.com/invalid.xml") }.should raise_error 16 | end 17 | 18 | context "with a valid podcast url" do 19 | it "should have standard podcast attributes" do 20 | podcast = House::Podcast.new("http://podcasts.example.com/valid.xml") 21 | 22 | podcast.title.should_not be_nil 23 | podcast.description.should_not be_nil 24 | podcast.language.should_not be_nil 25 | podcast.publish_date.should_not be_nil 26 | podcast.last_build_date.should_not be_nil 27 | podcast.site_link.should_not be_nil 28 | 29 | podcast.image_url.should_not be_nil 30 | podcast.image_title.should_not be_nil 31 | podcast.image_link.should_not be_nil 32 | end 33 | 34 | it "should have the itunes podcast attributes" do 35 | podcast = House::Podcast.new("http://podcasts.example.com/valid_with_itunes.xml") 36 | 37 | podcast.keywords.should_not be_empty 38 | podcast.author.should_not be_nil 39 | podcast.subtitle.should_not be_nil 40 | podcast.summary.should_not be_nil 41 | podcast.explicit?.should be_falsey 42 | end 43 | 44 | it "should be valid without the itunes podcast attributes" do 45 | podcast = House::Podcast.new("http://podcasts.example.com/valid_without_itunes.xml") 46 | 47 | podcast.keywords.should be_empty 48 | podcast.author.should be_nil 49 | podcast.subtitle.should be_nil 50 | podcast.summary.should be_nil 51 | podcast.explicit?.should be_nil 52 | end 53 | 54 | it "should be valid without podcast items" do 55 | podcast = House::Podcast.new("http://podcasts.example.com/valid_without_items.xml") 56 | 57 | podcast.items.should be_empty 58 | end 59 | 60 | it "should have the correct amount of podcast items" do 61 | podcast = House::Podcast.new("http://podcasts.example.com/valid_with_2_items.xml") 62 | 63 | podcast.items.size.should == 2 64 | end 65 | 66 | context "with a podcast item" do 67 | it "should have the standard item attributes" do 68 | podcast = House::Podcast.new("http://podcasts.example.com/valid_with_2_items.xml") 69 | item = podcast.items.first 70 | item.title.should_not be_nil 71 | item.description.should_not be_nil 72 | item.url.should_not be_nil 73 | end 74 | 75 | it "should have the itunes item attributes" do 76 | podcast = House::Podcast.new("http://podcasts.example.com/valid_with_2_items.xml") 77 | item = podcast.items.first 78 | item.title.should_not be_nil 79 | item.subtitle.should_not be_nil 80 | item.keywords.should_not be_empty 81 | item.duration.should_not be_nil 82 | item.url.should_not be_nil 83 | end 84 | 85 | it "should be valid without the itunes item attributes" do 86 | podcast = House::Podcast.new("http://podcasts.example.com/valid_with_items_without_itunes.xml") 87 | item = podcast.items.first 88 | item.subtitle.should be_nil 89 | item.summary.should be_nil 90 | item.keywords.should be_empty 91 | item.duration.should be_nil 92 | end 93 | 94 | context "downloads" do 95 | before(:each) do 96 | @save_dir = "/tmp/rspec" 97 | @podcast = House::Podcast.new("http://podcasts.example.com/valid_with_2_items.xml") 98 | @item = @podcast.items.first 99 | end 100 | 101 | after(:each) do 102 | # remove any temp files 103 | Dir.foreach(@save_dir) do |f| 104 | file_to_remove = File.join(@save_dir, f) 105 | File.delete(file_to_remove) if f != "." && f != ".." 106 | end 107 | end 108 | 109 | it "allows the item to be downloaded to the specified directory" do 110 | @item.download_to(File.join(@save_dir, "example2.m4a")) 111 | File.exists?(File.join(@save_dir, "example2.m4a")) 112 | end 113 | 114 | it "allows the podcast image to be downloaded to the specified directory" do 115 | @podcast.download_image_to(File.join(@save_dir, "example2.jpg")) 116 | File.exists?(File.join(@save_dir, "example2.jpg")) 117 | end 118 | end 119 | end 120 | 121 | context "updated since" do 122 | it "will confirm if a newer version has been released" do 123 | podcast = House::Podcast.new("http://podcasts.example.com/valid_future.xml") 124 | podcast.updated_since?(Time.now).should be_truthy 125 | #check latest item/pubdate 126 | end 127 | 128 | it "will confirm if a newer version has NOT been released" do 129 | podcast = House::Podcast.new("http://podcasts.example.com/valid_past.xml") 130 | podcast.updated_since?(Time.now).should be_falsey 131 | end 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "rspec" 2 | require "sham_rack" 3 | require "rack" 4 | require "house" 5 | 6 | ShamRack.at("podcasts.example.com").sinatra do 7 | get "/redirect.xml" do 8 | res = Rack::Response.new 9 | res.redirect("/valid.xml") 10 | res.write("302 you've redirected") 11 | res.finish 12 | end 13 | 14 | get "/:file_name" do 15 | File.read(File.join("spec", "test_data", params[:file_name])) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/test_data/example1.jpg: -------------------------------------------------------------------------------- 1 | Example JPEG/Users/luke/Dropbox/ruby/podcast_reader/spec/test_data/example1.m4a -------------------------------------------------------------------------------- /spec/test_data/example1.m4a: -------------------------------------------------------------------------------- 1 | Example 1 m4a -------------------------------------------------------------------------------- /spec/test_data/example2.jpg: -------------------------------------------------------------------------------- 1 | Example 2 jpg -------------------------------------------------------------------------------- /spec/test_data/example2.m4a: -------------------------------------------------------------------------------- 1 | Example 2 m4a -------------------------------------------------------------------------------- /spec/test_data/invalid.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |