canvas-lms/app/models/external_feed.rb

219 lines
8.4 KiB
Ruby

#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
class ExternalFeed < ActiveRecord::Base
attr_accessible :url, :verbosity, :header_match
belongs_to :user
belongs_to :context, :polymorphic => true
has_many :external_feed_entries, :dependent => :destroy
before_save :infer_defaults
def infer_defaults
self.consecutive_failures ||= 0
self.refresh_at ||= Time.now.utc
end
protected :infer_defaults
def display_name(short=true)
short_url = (self.url || "").split("/")[0,3].join("/")
res = self.title || (short ? t(:short_feed_title, "%{short_url} feed", :short_url => short_url) : self.url )
end
named_scope :to_be_polled, lambda {
{ :conditions => ['external_feeds.consecutive_failures < ? and external_feeds.refresh_at < ?', 5, Time.now ], :order => :refresh_at }
}
named_scope :for, lambda {|obj|
{ :conditions => ['external_feeds.feed_purpose = ?', obj] }
}
def add_rss_entries(rss)
items = rss.items.map{|item| add_entry(item, rss, :rss) }.compact
self.context.add_aggregate_entries(items, self) if self.context && self.context.respond_to?(:add_aggregate_entries)
items
end
def add_atom_entries(atom)
items = []
atom.each_entry{|item| items << add_entry(item, atom, :atom) }
items.compact!
self.context.add_aggregate_entries(items, self) if self.context && self.context.respond_to?(:add_aggregate_entries)
items
end
def add_ical_entries(cal)
items = cal.events.map{|event| add_entry(event, cal, :ical) }.compact
self.context.add_aggregate_entries(items, self) if self.context && self.context.respond_to?(:add_aggregate_entries)
items
end
def format_description(desc)
desc = (desc || "").to_s
if self.verbosity == 'link_only'
""
elsif self.verbosity == 'truncate'
self.extend TextHelper
truncate_html(desc, :max_length => 250)
else
desc
end
end
def add_entry(item, feed, feed_type)
if feed_type == :rss
uuid = (item.respond_to?(:guid) && item.guid && item.guid.content.to_s) || Digest::MD5.hexdigest("#{item.title}#{item.date.strftime('%Y-%m-%d')}")
entry = self.external_feed_entries.find_by_uuid(uuid)
entry ||= self.external_feed_entries.find_by_url(item.link)
description = entry && entry.message
if !description || description.empty?
description = "<a href='#{item.link}'>#{t :original_article, "Original article"}</a><br/><br/>"
description += format_description(item.description || item.title)
end
if entry
entry.update_feed_attributes(
:title => item.title,
:message => description,
:url => item.link
)
return entry
end
date = (item.respond_to?(:date) && item.date) || Date.today
return nil if self.header_match && !item.title.match(Regexp.new(self.header_match, true))
return nil if self.body_match && !item.description.match(Regexp.new(self.body_match, true))
return nil if (date && self.created_at > date rescue false)
description = "<a href='#{item.link}'>#{t :original_article, "Original article"}</a><br/><br/>"
description += format_description(item.description || item.title)
entry = self.external_feed_entries.create(
:title => item.title,
:message => description,
:source_name => feed.channel.title,
:source_url => feed.channel.link,
:posted_at => Time.parse(date.to_s),
:user => self.user,
:url => item.link,
:uuid => uuid
)
elsif feed_type == :atom
uuid = item.id || Digest::MD5.hexdigest("#{item.title}#{item.published.utc.strftime('%Y-%m-%d')}")
entry = self.external_feed_entries.find_by_uuid(uuid)
entry ||= self.external_feed_entries.find_by_url(item.links.alternate.to_s)
description = entry && entry.message
if !description || description.empty?
description = "<a href='#{item.links.alternate.to_s}'>#{t :original_article, "Original article"}</a><br/><br/>"
description += format_description(item.content || item.title)
end
if entry
entry.update_feed_attributes(
:title => item.title,
:message => description,
:url => item.links.alternate.to_s,
:author_name => author.name,
:author_url => author.uri,
:author_email => author.email
)
return entry
end
return nil if self.header_match && !item.title.match(Regexp.new(self.header_match, true))
return nil if self.body_match && !item.content.match(Regexp.new(self.body_match, true))
return nil if (item.published && self.created_at > item.published rescue false)
author = item.authors.first || OpenObject.new
description = "<a href='#{item.links.alternate.to_s}'>#{t :original_article, "Original article"}</a><br/><br/>"
description += format_description(item.content || item.title)
entry = self.external_feed_entries.create(
:title => item.title,
:message => description,
:source_name => feed.title,
:source_url => feed.links.alternate.to_s,
:posted_at => item.published,
:url => item.links.alternate.to_s,
:user => self.user,
:author_name => author.name,
:author_url => author.uri,
:author_email => author.email,
:uuid => uuid
)
elsif feed_type == :ical
entry = self.external_feed_entries.find_by_uuid(uuid)
entry ||= self.external_feed_entries.find_by_title_and_url(item.summary, item.url)
description = entry && entry.message
if !description || description.empty?
description = "<a href='#{item.url}'>#{t :original_article, "Original article"}</a><br/><br/>"
description += (item.description || item.summary).to_s
end
if entry
entry.update_feed_attributes(
:title => item.summary,
:message => description,
:url => item.url,
:start_at => item.start,
:end_at => item.end
)
entry.cancel_it if item.status.downcase == 'cancelled' && entry.active?
return entry
end
description = (item.description || item.summary).to_s
description += "<br/><br/><a href='#{item.url}'>#{item.url}</a>"
entry = self.external_feed_entries.create(
:title => item.summary,
:message => description,
:source_name => self.title,
:source_url => self.url,
:posted_at => item.timestamp,
:start_at => item.start,
:end_at => item.end,
:url => item.url,
:user => self.user
)
end
end
def self.process_migration(data, migration)
tools = data['external_feeds'] ? data['external_feeds']: []
to_import = migration.to_import 'external_feeds'
tools.each do |tool|
if tool['migration_id'] && (!to_import || to_import[tool['migration_id']])
begin
import_from_migration(tool, migration.context)
rescue
migration.add_warning("Couldn't import external feed \"#{tool[:title]}\"", $!)
end
end
end
end
def self.import_from_migration(hash, context, item=nil)
hash = hash.with_indifferent_access
return nil if hash[:migration_id] && hash[:external_feeds_to_import] && !hash[:external_feeds_to_import][hash[:migration_id]]
item ||= find_by_context_id_and_context_type_and_migration_id(context.id, context.class.to_s, hash[:migration_id]) if hash[:migration_id]
item ||= context.external_feeds.new
item.migration_id = hash[:migration_id]
item.url = hash[:url]
item.title = hash[:title]
item.feed_type = hash[:feed_type]
item.feed_purpose = hash[:purpose]
item.verbosity = hash[:verbosity]
item.header_match = hash[:header_match] unless hash[:header_match].blank?
item.save!
context.imported_migration_items << item if context.imported_migration_items && item.new_record?
item
end
end