diff --git a/app/controllers/my_profile/profile_design_controller.rb b/app/controllers/my_profile/profile_design_controller.rb index 4b8a870..791bc90 100644 --- a/app/controllers/my_profile/profile_design_controller.rb +++ b/app/controllers/my_profile/profile_design_controller.rb @@ -5,7 +5,7 @@ class ProfileDesignController < BoxOrganizerController protect 'edit_profile_design', :profile def available_blocks - blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock ] + blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock ] # blocks exclusive for organizations if profile.has_members? diff --git a/app/models/feed_reader_block.rb b/app/models/feed_reader_block.rb new file mode 100644 index 0000000..654ceea --- /dev/null +++ b/app/models/feed_reader_block.rb @@ -0,0 +1,57 @@ +class FeedReaderBlock < Block + + include DatesHelper + + settings_items :address, :type => :string + settings_items :limit, :type => :integer + settings_items :fetched_at, :type => :date + + settings_items :feed_title, :type => :string + settings_items :feed_items, :type => :array + + before_create do |block| + block.limit = 5 + block.feed_items = [] + end + + def self.description + _('List the latest N posts from a given RSS feed.') + end + + def help + _('This block can be used to create a list of latest N posts from a given RSS feed. You should only enter the RSS feed address.') + end + + def default_title + self.feed_title.nil? ? _('Feed Reader') : self.feed_title + end + + def formatted_feed_content + if self.fetched_at.nil? or self.feed_items.empty? + return ("

%s

" % _('Feed content was not loaded yet')) + else + return "" + + "
#{_("Updated: %s") % show_date(self.fetched_at)}
" + end + end + + def add_item(title, link, date, content) + self.feed_items << {:title => title, :link => link} + end + + def clean + self.feed_items = [] + self.feed_title = nil + end + + def content + block_title(title) + formatted_feed_content + end + + def editable? + true + end + +end diff --git a/app/views/box_organizer/_feed_reader_block.rhtml b/app/views/box_organizer/_feed_reader_block.rhtml new file mode 100644 index 0000000..726d51f --- /dev/null +++ b/app/views/box_organizer/_feed_reader_block.rhtml @@ -0,0 +1,4 @@ +
+ <%= labelled_form_field _('Address'), text_field(:block, :address, :size => 40) %> + <%= labelled_form_field _('Limit of posts to display'), select(:block, :limit, [5, 10, 20, 50]) %> +
diff --git a/lib/feed_handler.rb b/lib/feed_handler.rb new file mode 100644 index 0000000..3a273f2 --- /dev/null +++ b/lib/feed_handler.rb @@ -0,0 +1,38 @@ +require 'feedparser' +require 'open-uri' + +class FeedHandler + + def parse(content) + raise FeedHandler::ParseError, "Content is nil" if content.nil? + begin + return FeedParser::Feed::new(content) + rescue Exception => ex + raise FeedHandler::ParseError, ex.to_s + end + end + + def fetch(address) + begin + content = "" + open(address) do |s| content = s.read end + return content + rescue Exception => ex + raise FeedHandler::FetchError, ex.to_s + end + end + + def process(container) + content = fetch(container.address) + container.fetched_at = Time.now + parse = parse(content) + container.feed_title = parse.title + parse.items[0..container.limit-1].each do |item| + container.add_item(item.title, item.link, item.date, item.content) + end + end + + class ParseError < Exception; end + class FetchError < Exception; end + +end diff --git a/public/stylesheets/blocks/feed-reader-block.css b/public/stylesheets/blocks/feed-reader-block.css new file mode 100644 index 0000000..2058ed1 --- /dev/null +++ b/public/stylesheets/blocks/feed-reader-block.css @@ -0,0 +1,5 @@ +.feed-reader-block-fetched-at { + color: #CCC; + font-size: 11px; + text-align: center; +} diff --git a/script/feed-updater b/script/feed-updater new file mode 100755 index 0000000..db2e2bd --- /dev/null +++ b/script/feed-updater @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/environment' + +FeedReaderBlock.find(:all).each do |feed_block| + unless feed_block.address.nil? + begin + feed_block.clean + handler = FeedHandler.new + handler.process(feed_block) + feed_block.save! + RAILS_DEFAULT_LOGGER.info("%s ID %d fetched at %s" % [feed_block.class.name, feed_block.id, feed_block.fetched_at]) + rescue FeedHandler::ParseError => ex + RAILS_DEFAULT_LOGGER.warn("Error parsing content from %s ID %d\n%s" % [feed_block.class.name, feed_block.id, ex.to_s]) + rescue FeedHandler::FetchError => ex + RAILS_DEFAULT_LOGGER.warn("Error fetching content from %s ID %d\n%s" % [feed_block.class.name, feed_block.id, ex.to_s]) + rescue Exception => ex + RAILS_DEFAULT_LOGGER.warn("Unknown error from %s ID %d\n%s" % [feed_block.class.name, feed_block.id, ex.to_s]) + end + end +end diff --git a/test/fixtures/files/feed.xml b/test/fixtures/files/feed.xml new file mode 100644 index 0000000..cdf1d3c --- /dev/null +++ b/test/fixtures/files/feed.xml @@ -0,0 +1,44 @@ + + + + Feed for unit tests + http://localhost/feed-test + Feed content + pt_BR + + Last POST + + Cursus justo nec urna. Suspendisse potenti. In hac + habitasse platea dictumst. Cras quis lacus. Vestibulum rhoncus + congue lacus. + + Wed, 11 Mar 2009 19:15:57 -0300 + http://localhost/last-post + http://localhost/last-post + + + Second POST + + Suspendisse tincidunt mi vel metus. Vivamus non urna in nisi gravida congue. + Aenean semper orci a eros. Praesent dictum. Maecenas pharetra odio ut dui. + Pellentesque ut orci. Sed lobortis, velit at laoreet suscipit, quam est + sagittis nibh. + + Wed, 18 Feb 2009 11:01:53 -0300 + http://localhost/second-post + http://localhost/second-post + + + First POST + + Lacus at sapien suscipit tempus. Proin pulvinar velit + sed nulla. Curabitur aliquet leo ac massa. Praesent posuere lectus + vitae odio. Donec imperdiet urna vel ante. In semper accumsan diam. + Vestibulum porta justo. Suspendisse egestas commodo eros. + + Sun, 18 Jan 2009 11:34:30 -0300 + http://localhost/first-post + http://localhost/first-post + + + diff --git a/test/functional/profile_design_controller_test.rb b/test/functional/profile_design_controller_test.rb index d58ac19..dd97196 100644 --- a/test/functional/profile_design_controller_test.rb +++ b/test/functional/profile_design_controller_test.rb @@ -289,4 +289,28 @@ class ProfileDesignControllerTest < Test::Unit::TestCase assert_equal !state, block.visible? end + should 'offer to create feed reader block' do + get :add_block, :profile => 'designtestuser' + assert_tag :tag => 'input', :attributes => { :id => 'type_feedreaderblock', :value => 'FeedReaderBlock' } + end + + should 'be able to edit FeedReaderBlock' do + @box1.blocks << FeedReaderBlock.new(:address => 'feed address') + + get :edit, :profile => 'designtestuser', :id => @box1.blocks[-1].id + + assert_response :success + assert_tag :tag => 'input', :attributes => { :name => "block[address]", :value => 'feed address' } + assert_tag :tag => 'select', :attributes => { :name => "block[limit]" } + end + + should 'be able to save FeedReaderBlock configurations' do + @box1.blocks << FeedReaderBlock.new(:address => 'feed address') + + post :save, :profile => 'designtestuser', :id => @box1.blocks[-1].id, :block => {:address => 'new feed address', :limit => '20'} + + assert_equal 'new feed address', @box1.blocks[-1].address + assert_equal 20, @box1.blocks[-1].limit + end + end diff --git a/test/unit/feed_handler_test.rb b/test/unit/feed_handler_test.rb new file mode 100644 index 0000000..4e214f3 --- /dev/null +++ b/test/unit/feed_handler_test.rb @@ -0,0 +1,83 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class FeedHandlerTest < Test::Unit::TestCase + + class FeedContainer + attr_accessor :limit + attr_accessor :fetched_at + attr_accessor :feed_title + attr_accessor :feed_items + attr_accessor :address + def initialize + self.limit = 5 + self.feed_title = "Feed Container Mocked" + self.feed_items = [] + self.address = 'test/fixtures/files/feed.xml' + end + def add_item(title, link, date, content) + self.feed_items << title + end + end + + def setup + @handler = FeedHandler.new + @container = FeedContainer.new + end + attr_reader :handler, :container + + should 'fetch feed content' do + content = handler.fetch(container.address) + assert_match /Feed content<\/description>/, content + assert_match /Feed for unit tests<\/title>/, content + end + + should 'parse feed content' do + content = "" + open(container.address) do |s| content = s.read end + parse = handler.parse(content) + assert_equal 'Feed for unit tests', parse.title + assert_equal 'http://localhost/feed-test', parse.link + assert_equal 'Last POST', parse.items[0].title + end + + should 'process feed and populate container' do + handler.process(container) + assert_equal 'Feed for unit tests', container.feed_title + assert_equal ["Last POST", "Second POST", "First POST"], container.feed_items + end + + should 'raise exception when parser nil' do + handler = FeedHandler.new + assert_raise FeedHandler::ParseError do + handler.parse(nil) + end + end + + should 'raise exception when parser invalid content' do + handler = FeedHandler.new + assert_raise FeedHandler::ParseError do + handler.parse('<invalid>content</invalid>') + end + end + + should 'raise exception when fetch nil' do + handler = FeedHandler.new + assert_raise FeedHandler::FetchError do + handler.fetch(nil) + end + end + + should 'raise exception when fetch invalid address' do + handler = FeedHandler.new + assert_raise FeedHandler::FetchError do + handler.fetch('bli://invalid@address') + end + end + + should 'save only latest N posts from feed' do + container.limit = 1 + handler.process(container) + assert_equal 1, container.feed_items.size + end + +end diff --git a/test/unit/feed_reader_block_test.rb b/test/unit/feed_reader_block_test.rb new file mode 100644 index 0000000..0443274 --- /dev/null +++ b/test/unit/feed_reader_block_test.rb @@ -0,0 +1,73 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class FeedReaderBlockTest < ActiveSupport::TestCase + + include DatesHelper + + def setup + @feed = FeedReaderBlock.new + @fetched_at = Time.now + @feed.fetched_at = @fetched_at + @feed.save! + end + attr_reader :feed, :fetched_at + + should 'default describe' do + assert_not_equal Block.description, FeedReaderBlock.description + end + + should 'have address and limit' do + assert_respond_to feed, :address + assert_respond_to feed, :limit + end + + should 'default value of limit' do + assert_equal 5, feed.limit + end + + should 'is editable' do + assert feed.editable? + end + + should 'display feed posts from content' do + feed.feed_items = [] + %w[ last-post second-post first-post ].each do |i| + feed.feed_items << {:title => i, :link => "http://localhost/#{i}"} + end + feed.feed_title = 'Feed for unit tests' + feed_content = feed.content + assert_tag_in_string feed_content, :tag => 'h3', :content => 'Feed for unit tests' + assert_tag_in_string feed_content, :tag => 'a', :attributes => { :href => 'http://localhost/last-post' }, :content => 'last-post' + assert_tag_in_string feed_content, :tag => 'a', :attributes => { :href => 'http://localhost/second-post' }, :content => 'second-post' + assert_tag_in_string feed_content, :tag => 'a', :attributes => { :href => 'http://localhost/first-post' }, :content => 'first-post' + end + + should 'display channel title as title by default' do + feed.feed_title = 'Feed for unit tests' + assert_equal 'Feed for unit tests', feed.title + end + + should 'display default title when hasnt feed_content' do + assert_equal 'Feed Reader', feed.title + end + + should 'notice when content not fetched yet' do + assert_tag_in_string feed.content, :tag => 'p', :content => 'Feed content was not loaded yet' + end + + should 'display last fetched date' do + feed.feed_items = ['one', 'two'] + assert_tag_in_string feed.content, :tag => 'div', + :content => "Updated: #{show_date(@fetched_at)}", + :attributes => {:class => 'feed-reader-block-fetched-at'} + end + + should 'clear feed title and items' do + feed.feed_items = %w[ last-post second-post first-post ] + feed.feed_title = 'Feed Test' + feed.clean + assert_nil feed.feed_title + assert_equal [], feed.feed_items + end + +end -- libgit2 0.21.2