#!/usr/bin/ruby require 'pathname' require 'rss/maker' require 'atom' require 'uuidtools' require 'time' require 'socket' require 'configurability' require 'strelka' require 'strelka/mixins' require 'strelka/cms' require 'strelka/cms/pagecatalog' require 'strelka/cms/page' # The Feed applet -- generate RSS and Atom feeds for various things. class Strelka::CMS::ContentFeeds < Strelka::App extend Configurability, Strelka::MethodUtilities include UUIDTools # Configurability API -- configure this app with the 'feeds' section of the # config. config_key :content_feeds # Strelka app ID ID = 'content-feeds' # Configuration defaults DEFAULT_CONFIG = { uuid: UUID.sha1_create( UUID_DNS_NAMESPACE, Socket.gethostname ).to_s, entry_count: 10, pageroot: Pathname( __FILE__ ).dirname.parent.parent.parent + 'public', paths: [''], } # The feed's site UUID (for Atom feeds) singleton_attr_accessor :uuid # The number of entries to include in a feed singleton_attr_accessor :entry_count # The page root singleton_attr_accessor :pageroot # The paths, relative to the pageroot, that can be served as a feed singleton_attr_accessor :paths ### Configurability API -- configure the app. def self::configure( config=nil ) if config self.uuid = config[:uuid] self.entry_count = config[:entry_count] self.pageroot = config[:pageroot] self.paths = config[:paths] end end ### Set up the feeds app. def initialize( * ) super @catalog = Strelka::CMS::PageCatalog.new( self.class.pageroot ) @site_uuid = UUID.parse( self.class.uuid ) end ###### public ###### # The PageCatalog to use for building feeds attr_reader :catalog # # Routing # plugin :routing get 'rss' do |req| feedpath = req.app_path[ %r{/rss/(.*)}, 1 ] newest_pages = self.find_newest_pages( feedpath, req ) baseuri = req.uri baseuri.path = '' # Generate the feed out of the newest 10 pages feed = RSS::Maker.make( '2.0' ) do |feed| feed.channel.title = "DevEiate: %s" % [ feedpath.capitalize ] feed.channel.link = "http://deveiate.org/#{feedpath}/" feed.channel.description = "Making stuff up since 1994" feed.items.do_sort = true # sort items by date newest_pages.each do |page| entry_url = baseuri.dup entry_url.path = '/' + File.join( feedpath, page.relative_html_path ) item = feed.items.new_item item.title = page.title item.link = entry_url item.date = page.date end end res = req.response # Set the cache header and set the mimetype res.headers.last_modified = newest_pages.first.date.httpdate res.content_type = 'application/rss+xml' res.body = feed.to_s return res end ### Action: Return an ATOM feed for the pages under the path made up of +pathparts+. get 'atom' do |req| feedpath = req.app_path[ %r{/atom/(.*)}, 1 ] newest_pages = self.find_newest_pages( feedpath, req ) newest_page = newest_pages.first baseuri = req.uri baseuri.path = '' feed = Atom::Feed.new do |feed| feed.title = "DevEiate: %s" % [ feedpath.capitalize ] feed.links << Atom::Link.new( :href => baseuri ) feed.updated = newest_page.date feed.authors << Atom::Person.new( :name => 'Michael Granger' ) feed.id = @site_uuid.to_uri newest_pages.each do |page| entry_url = baseuri.dup entry_url.path = '/' + File.join( feedpath, page.relative_html_path ) feed.entries << Atom::Entry.new do |entry| entry.title = page.title entry.id = UUID.sha1_create( UUID_URL_NAMESPACE, entry_url.to_s ).to_uri entry.updated = page.date entry.summary = page.summary entry.links << Atom::Link.new( :href => entry_url ) end end end res = req.response # Set the cache header and set the mimetype res.headers.last_modified = newest_page.date.httpdate res.content_type = 'application/atom+xml' res.body = feed.to_xml return res end ######### protected ######### ### Find the newest pages for the path of the +request+ and return them. def find_newest_pages( feedpath, req ) self.log.debug "Looking for newest pages in feed path: %p" % [ feedpath ] finish_with HTTP::NOT_FOUND unless self.class.paths.include?( feedpath ) feedpath.untaint count = self.class.entry_count newest_pages = self.catalog.relative_to( feedpath ).sort_by( &:date ).first( count ) newest_page = newest_pages.first # Just 304 if the feed doesn't need to be updated if req.headers.if_modified_since && Time.parse(req.headers.if_modified_since) > newest_page.date self.log.info "Not modified since %s" % [ req.headers.if_modified_since ] finish_with HTTP::NOT_MODIFIED, "No changes since %s" % [ req.headers.if_modified_since ] end return newest_pages end end # class Strelka::CMS::Feeds Encoding.default_internal = Encoding::UTF_8 Strelka::CMS::Feeds.run if __FILE__ == $0