2020-12-12 21:59:04 +01:00
|
|
|
# typed: false
|
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2020-12-13 12:23:20 +01:00
|
|
|
require "bundle_version"
|
2020-12-12 21:59:04 +01:00
|
|
|
require_relative "page_match"
|
|
|
|
|
|
|
|
module Homebrew
|
|
|
|
module Livecheck
|
|
|
|
module Strategy
|
|
|
|
# The {Sparkle} strategy fetches content at a URL and parses
|
|
|
|
# its contents as a Sparkle appcast in XML format.
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
class Sparkle
|
|
|
|
extend T::Sig
|
|
|
|
|
|
|
|
NICE_NAME = "Sparkle"
|
|
|
|
|
|
|
|
PRIORITY = 1
|
|
|
|
|
|
|
|
# Whether the strategy can be applied to the provided URL.
|
|
|
|
sig { params(url: String).returns(T::Boolean) }
|
|
|
|
def self.match?(url)
|
2020-12-14 02:35:26 +01:00
|
|
|
return false unless url.match?(%r{^https?://})
|
|
|
|
|
2020-12-14 04:35:26 +01:00
|
|
|
xml = url.end_with?(".xml")
|
2020-12-14 02:35:26 +01:00
|
|
|
xml ||= begin
|
|
|
|
headers = Strategy.page_headers(url)
|
2020-12-14 04:35:26 +01:00
|
|
|
content_type = headers["content-type"]&.split(";", 2)&.first
|
2020-12-14 02:35:26 +01:00
|
|
|
["application/xml", "text/xml"].include?(content_type)
|
|
|
|
end
|
|
|
|
return false unless xml
|
|
|
|
|
|
|
|
contents = Strategy.page_contents(url)
|
2020-12-14 05:41:41 +01:00
|
|
|
|
|
|
|
return true if contents.match?(%r{https?://www.andymatuschak.org/xml-namespaces/sparkle})
|
|
|
|
|
|
|
|
contents.include?("rss") &&
|
|
|
|
contents.include?("channel") &&
|
|
|
|
contents.include?("item") &&
|
|
|
|
contents.include?("enclosure")
|
2020-12-12 21:59:04 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
# Checks the content at the URL for new versions.
|
|
|
|
sig { params(url: String, regex: T.nilable(Regexp)).returns(T::Hash[Symbol, T.untyped]) }
|
2020-12-13 12:23:20 +01:00
|
|
|
def self.find_versions(url, regex, &block)
|
2020-12-12 21:59:04 +01:00
|
|
|
raise ArgumentError, "The #{NICE_NAME} strategy does not support regular expressions." if regex
|
|
|
|
|
|
|
|
require "nokogiri"
|
|
|
|
|
|
|
|
match_data = { matches: {}, regex: regex, url: url }
|
|
|
|
|
|
|
|
contents = Strategy.page_contents(url)
|
|
|
|
|
2020-12-14 05:04:14 +01:00
|
|
|
xml = Nokogiri::XML(contents)
|
2020-12-12 21:59:04 +01:00
|
|
|
xml.remove_namespaces!
|
|
|
|
|
2020-12-14 05:04:14 +01:00
|
|
|
items = xml.xpath("//rss//channel//item").map do |item|
|
|
|
|
enclosure = (item > "enclosure").first
|
2020-12-12 21:59:04 +01:00
|
|
|
|
2020-12-14 05:04:14 +01:00
|
|
|
next unless enclosure
|
|
|
|
|
|
|
|
short_version ||= enclosure["shortVersionString"]
|
|
|
|
version ||= enclosure["version"]
|
|
|
|
|
|
|
|
short_version ||= (item > "shortVersionString").first&.text
|
|
|
|
version ||= (item > "version").first&.text
|
|
|
|
|
2020-12-14 05:41:41 +01:00
|
|
|
data = {
|
2020-12-14 09:55:01 +01:00
|
|
|
title: (item > "title").first&.text,
|
2020-12-14 05:04:14 +01:00
|
|
|
url: enclosure["url"],
|
2020-12-14 10:34:14 +01:00
|
|
|
bundle_version: short_version || version ? BundleVersion.new(short_version, version) : nil,
|
2020-12-14 05:41:41 +01:00
|
|
|
}.compact
|
|
|
|
|
|
|
|
data unless data.empty?
|
2020-12-14 05:04:14 +01:00
|
|
|
end.compact
|
|
|
|
|
2020-12-14 10:34:14 +01:00
|
|
|
item = items.max_by { |e| e[:bundle_version] }
|
2020-12-14 05:04:14 +01:00
|
|
|
|
|
|
|
if item
|
2020-12-13 12:23:20 +01:00
|
|
|
match = if block
|
2020-12-14 10:34:14 +01:00
|
|
|
item[:short_version] = item[:bundle_version]&.short_version
|
|
|
|
item[:version] = item[:bundle_version]&.version
|
2020-12-14 05:04:14 +01:00
|
|
|
block.call(item).to_s
|
2020-12-13 12:23:20 +01:00
|
|
|
else
|
2020-12-14 10:34:14 +01:00
|
|
|
item[:bundle_version]&.nice_version
|
2020-12-13 12:23:20 +01:00
|
|
|
end
|
|
|
|
|
2020-12-14 05:41:41 +01:00
|
|
|
match_data[:matches][match] = Version.new(match) if match
|
2020-12-13 12:23:20 +01:00
|
|
|
end
|
2020-12-12 21:59:04 +01:00
|
|
|
|
|
|
|
match_data
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|