class Motion::Project::Sparkle

Constants

CONFIG_PATH
RELEASE_PATH
SPARKLE_ROOT
TEMPLATE_PATHS
VERSION

Public Class Methods

new(config) click to toggle source
# File lib/motion/project/sparkle.rb, line 8
def initialize(config)
  @config = config
  publish :public_key, 'dsa_pub.pem'
  install_and_embed
end

Public Instance Methods

add_to_gitignore() click to toggle source

File manipulation and certificates

# File lib/motion/project/sparkle.rb, line 60
def add_to_gitignore
  @ignorable = ['sparkle/release','sparkle/release/*','sparkle/config/dsa_priv.pem']
  return unless File.exist?(gitignore_path)
  File.open(gitignore_path, 'r') do |f|
    f.each_line do |line|
      @ignorable.delete(line) if @ignorable.include?(line)
    end
  end
  File.open(gitignore_path, 'a') do |f|
    @ignorable.each do |i|
      f << "#{i}\n"
    end
  end if @ignorable.any?
  `cat #{gitignore_path}`
end
all_templates() click to toggle source
# File lib/motion/project/templates.rb, line 11
def all_templates
  @all_templates ||= begin
    templates = {}
    TEMPLATE_PATHS.map { |path| Dir.glob(path + '/*') }.flatten.each do |template_path|
      templates[File.basename(template_path)] = template_path
    end
    templates
  end
end
app_bundle_path() click to toggle source
# File lib/motion/project/sparkle.rb, line 162
def app_bundle_path
  Pathname.new @config.app_bundle_raw('MacOSX')
end
app_file() click to toggle source
# File lib/motion/project/sparkle.rb, line 178
def app_file
  "#{app_name}.app"
end
app_name() click to toggle source
# File lib/motion/project/sparkle.rb, line 170
def app_name
  File.basename(app_bundle_path, '.app')
end
app_release_path() click to toggle source
# File lib/motion/project/sparkle.rb, line 166
def app_release_path
  app_bundle_path.parent.to_s
end
appcast() click to toggle source
# File lib/motion/project/sparkle.rb, line 14
def appcast
  @appcast ||= Appcast.new
end
appcast_xml() click to toggle source
# File lib/motion/project/appcast.rb, line 32
def appcast_xml
  rss = REXML::Element.new 'rss'
  rss.attributes['xmlns:atom'] = "http://www.w3.org/2005/Atom"
  rss.attributes['xmlns:sparkle'] = "http://www.andymatuschak.org/xml-namespaces/sparkle"
  rss.attributes['xmlns:version'] = "2.0"
  rss.attributes['xmlns:dc'] = "http://purl.org/dc/elements/1.1/"
  channel = rss.add_element 'channel'
  channel.add_element('title').text = @config.name
  channel.add_element('description').text = "#{@config.name} updates"
  channel.add_element('link').text = @config.info_plist["SUFeedURL"]
  channel.add_element('language').text = 'en'
  channel.add_element('pubDate').text = Time.now.strftime("%a, %d %b %Y %H:%M:%S %z")
  atom_link = channel.add_element('atom:link')
  atom_link.attributes['href'] = @config.info_plist["SUFeedURL"]
  atom_link.attributes['rel'] = 'self'
  atom_link.attributes['type'] = "application/rss+xml"
  item = channel.add_element 'item'
  item.add_element('title').text = "#{@config.name} #{@config.version}"
  item.add_element('pubDate').text = Time.now.strftime("%a, %d %b %Y %H:%M:%S %z")
  guid = item.add_element('guid')
  guid.text = "#{@config.name}-#{@config.version}"
  guid.attributes['isPermaLink'] = false
  item.add_element('sparkle:releaseNotesLink').text = "#{appcast.notes_url}"
  enclosure = item.add_element('enclosure')
  enclosure.attributes['url'] = "#{appcast.package_url}"
  enclosure.attributes['length'] = "#{@package_size}"
  enclosure.attributes['type'] = "application/octet-stream"
  enclosure.attributes['sparkle:version'] = @config.version
  enclosure.attributes['sparkle:dsaSignature'] = @package_signature
  rss
end
certificates_ok?(silence=false) click to toggle source
# File lib/motion/project/setup.rb, line 40
def certificates_ok?(silence=false)
  unless File.exist?("./#{Sparkle::CONFIG_PATH}")
    if silence
      return false
    else
      App.fail "Missing `#{Sparkle::CONFIG_PATH}`. Run `rake sparkle:setup` to get started" 
    end
  end
  unless File.exist?(private_key_path)
    if silence
      return false
    else
      App.fail "Missing `#{private_key_path}`. Please run `rake sparkle:setup_certificates` or check the docs to know where to put them."
    end
  end
  unless File.exist?(public_key_path)
    if silence
      return false
    else
      App.fail "Missing `#{public_key_path}`. Did you configure `release :public_key` correctly in the Rakefile? Advanced: recreate your public key with `rake sparkle:recreate_public_key`"
    end
  end
  true
end
check_base_url() click to toggle source
# File lib/motion/project/setup.rb, line 15
def check_base_url
  base_url_check = appcast.base_url.to_s
  if base_url_check.nil? or base_url_check.empty?
    App.fail "Sparkle :base_url missing. Use `release :base_url, 'http://example.com/your_app_folder'` in your Rakefile's `app.sparkle` block"
  end
  true
end
check_feed_url() click to toggle source
# File lib/motion/project/setup.rb, line 23
def check_feed_url
  feed_url_check = @config.info_plist['SUFeedURL']
  feed_filename_check = appcast.feed_filename
  if feed_url_check.nil? or feed_url_check.empty? or feed_filename_check.nil? or feed_filename_check.empty?
    App.fail "Sparkle :feed_filename is nil or blank. Please check your Rakefile."
  end
  true
end
check_public_key() click to toggle source
# File lib/motion/project/setup.rb, line 32
def check_public_key
  public_key_check = @config.info_plist['SUPublicDSAKeyFile'].to_s
  if public_key_check.nil? or public_key_check.empty?
    App.fail "Sparkle :public_key is nil or blank. Please check your Rakefile."
  end
  true
end
config_ok?() click to toggle source
# File lib/motion/project/setup.rb, line 9
def config_ok?
  check_base_url
  check_feed_url
  check_public_key
end
copy_templates(force=false) click to toggle source
# File lib/motion/project/templates.rb, line 21
def copy_templates(force=false)
  all_templates.each_pair do |tmpl, path|
    result = "#{sparkle_config_path}/#{tmpl}"
    if File.exist?(result) and !force
      App.info 'Exists', result
    else
      FileUtils.cp(path, "#{sparkle_config_path}/")
      App.info 'Create', "./#{sparkle_config_path}/#{tmpl.to_s}"
    end
  end
end
copy_zipball() click to toggle source
# File lib/motion/project/install.rb, line 18
def copy_zipball
  `cp #{sparkle_distrib} #{sparkle_zipball}`
end
create_appcast() click to toggle source
# File lib/motion/project/appcast.rb, line 16
def create_appcast
  appcast_file = File.open("#{sparkle_release_path}/#{appcast.feed_filename}", 'w') do |f|
    xml_string = ''
    doc = REXML::Formatters::Pretty.new
    doc.write(appcast_xml, xml_string)
    f << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
    f << xml_string
    f << "\n"
  end
  if appcast_file
    App.info "Create", "./#{sparkle_release_path}/#{appcast.feed_filename}"
  else
    App.info "Fail", "./#{sparkle_release_path}/#{appcast.feed_filename} not created"
  end
end
create_config_folder() click to toggle source
# File lib/motion/project/sparkle.rb, line 81
def create_config_folder
  FileUtils.mkdir_p(sparkle_config_path) unless File.exist?(sparkle_config_path)
end
create_release_folder() click to toggle source
# File lib/motion/project/sparkle.rb, line 85
def create_release_folder
  FileUtils.mkdir_p(sparkle_release_path) unless File.exist?(sparkle_release_path)
end
create_release_notes() click to toggle source
# File lib/motion/project/appcast.rb, line 4
def create_release_notes
  if File.exist?(release_notes_template_path)
    File.open("#{release_notes_path}", "w") do |f|
      template = File.read(release_notes_template_path)
      f << ERB.new(template).result(binding)
    end
    App.info 'Create', "./#{release_notes_path}"
  else
    App.fail "Release notes template not found as expected at ./#{release_notes_template_path}"
  end
end
create_sparkle_folder() click to toggle source
# File lib/motion/project/sparkle.rb, line 76
def create_sparkle_folder
  create_config_folder
  create_release_folder
end
create_zip_file() click to toggle source
# File lib/motion/project/package.rb, line 19
def create_zip_file
  unless File.exist?(app_bundle_path)
    App.fail "You need to build your app with the Release target to use Sparkle"
  end
  if File.exist?("#{sparkle_release_path}/#{zip_file}")
    App.fail "Release already exists at ./#{sparkle_release_path}/#{zip_file} (remove it manually with `rake sparkle:clean`)"
  end
  FileUtils.cd(app_release_path) do
    `zip -r --symlinks "#{zip_file}" "#{app_file}"`
  end
  FileUtils.mv "#{app_release_path}/#{zip_file}", "./#{sparkle_release_path}/"
  App.info "Create", "./#{sparkle_release_path}/#{zip_file}"
  @package_file = zip_file
  @package_size = File.size "./#{sparkle_release_path}/#{zip_file}"
end
dsa_param_path() click to toggle source
# File lib/motion/project/sparkle.rb, line 149
def dsa_param_path
  sparkle_config_path + "dsaparam.pem"
end
embed() click to toggle source
# File lib/motion/project/install.rb, line 36
def embed
  @config.embedded_frameworks << sparkle_path
end
feed_url(url) click to toggle source
# File lib/motion/project/sparkle.rb, line 50
def feed_url(url)
  @config.info_plist['SUFeedURL'] = url
end
generate_keys() click to toggle source
# File lib/motion/project/sparkle.rb, line 89
    def generate_keys
      return false unless config_ok?
      unless File.exist?(sparkle_config_path)
        FileUtils.mkdir_p sparkle_config_path
      end
      [dsa_param_path, private_key_path, public_key_path].each do |file|
        if File.exist? file
          App.info "Sparkle", "Error: file exists.
There's already a '#{file}'. Be careful not to override or lose your certificates. \n
Delete this file if you're sure. \n
Aborting (no action performed)
          "
          return
        end
      end
      `#{openssl} dsaparam 1024 < /dev/urandom > #{dsa_param_path}`
      `#{openssl} gendsa #{dsa_param_path} -out #{private_key_path}`
      generate_public_key
      `rm #{dsa_param_path}`
      App.info "Sparkle", "Generated private and public certificates.
Details:
  *  Private certificate: ./#{private_key_path}
  *  Public certificate: ./#{public_key_path}
Warning:
ADD YOUR PRIVATE CERTIFICATE TO YOUR `.gitignore` OR EQUIVALENT AND BACK IT UP!
KEEP IT PRIVATE AND SAFE!
If you lose it, your users will be unable to upgrade.
      "
    end
generate_public_key() click to toggle source
# File lib/motion/project/sparkle.rb, line 119
def generate_public_key
  `#{openssl} dsa -in #{private_key_path} -pubout -out #{public_key_path}`
end
gitignore_path() click to toggle source
# File lib/motion/project/sparkle.rb, line 137
def gitignore_path
  project_path + ".gitignore"
end
install() click to toggle source
# File lib/motion/project/install.rb, line 31
def install
  copy_zipball
  unzip
end
install_and_embed() click to toggle source
# File lib/motion/project/install.rb, line 40
def install_and_embed
  install unless installed?
  embed
end
installed?() click to toggle source
# File lib/motion/project/install.rb, line 27
def installed?
  File.directory?(sparkle_path)
end
openssl() click to toggle source

A few helpers

# File lib/motion/project/sparkle.rb, line 125
def openssl
  "/usr/bin/openssl"
end
package() click to toggle source
# File lib/motion/project/package.rb, line 4
def package
  return unless setup_ok?
  create_release_folder
  @config.build_mode = :release
  return unless create_zip_file
  App.info "Release", version_string
  App.info "Version", @config.version
  App.info "Build", @config.short_version || 'unspecified in Rakefile'
  App.info "Size", @package_size.to_s
  sign_package
  create_appcast
  create_release_notes
  `open #{sparkle_release_path}`
end
private_key_path() click to toggle source
# File lib/motion/project/sparkle.rb, line 153
def private_key_path
  sparkle_config_path + "dsa_priv.pem"
end
project_path() click to toggle source
# File lib/motion/project/sparkle.rb, line 129
def project_path
  @project_path ||= Pathname.new(@config.project_dir)
end
public_key(path_in_resources_folder) click to toggle source
# File lib/motion/project/sparkle.rb, line 54
def public_key(path_in_resources_folder)
  @config.info_plist['SUPublicDSAKeyFile'] = path_in_resources_folder
end
public_key_path() click to toggle source
# File lib/motion/project/sparkle.rb, line 157
def public_key_path
  pub_key_file = @config.info_plist['SUPublicDSAKeyFile']
  project_path + "resources/#{pub_key_file}"
end
publish(key, value) click to toggle source
# File lib/motion/project/sparkle.rb, line 18
def publish(key, value)
  case key
  when :public_key
    public_key value
  when :base_url
    appcast.base_url = value
    feed_url appcast.feed_url
  when :feed_base_url
    appcast.feed_base_url = value
    feed_url appcast.feed_url
  when :feed_filename
    appcast.feed_filename = value
    feed_url appcast.feed_url
  when :version
    version value
  when :notes_base_url, :package_base_url, :notes_filename, :package_filename
    appcast.send "#{key}=", value
  else
    raise "Unknown Sparkle config option #{key}"
  end
end
Also aliased as: release
release(key, value)
Alias for: publish
release_notes_content() click to toggle source
# File lib/motion/project/appcast.rb, line 76
def release_notes_content
  if File.exist?(release_notes_content_path)
    File.read(release_notes_content_path)
  else
    App.fail "Missing #{release_notes_content_path}"
  end
end
release_notes_content_path() click to toggle source
# File lib/motion/project/appcast.rb, line 68
def release_notes_content_path
  sparkle_config_path + "release_notes.content.html"
end
release_notes_html() click to toggle source
# File lib/motion/project/appcast.rb, line 84
def release_notes_html
  release_notes_content
end
release_notes_path() click to toggle source
# File lib/motion/project/appcast.rb, line 72
def release_notes_path
  sparkle_release_path + appcast.notes_filename.to_s
end
release_notes_template_path() click to toggle source
# File lib/motion/project/appcast.rb, line 64
def release_notes_template_path
  sparkle_config_path + "release_notes.template.erb"
end
setup() click to toggle source
# File lib/motion/project/setup.rb, line 65
    def setup
      verify_installation
      create_sparkle_folder
      add_to_gitignore
      copy_templates

      if config_ok?
        App.info "Sparkle", "Config found"
      else
        return false
      end

      silence = true
      if certificates_ok?(silence)
        App.info "Sparkle", "Certificates found"
      else
        App.info "Sparkle", "Certificates not found
Please generate your private and public keys with
    `rake sparkle:setup_certificates`
If you already have your certificates and only need to include them in the project, follow these steps:
    1. Rename your private key to `./#{private_key_path}`
    2. Place your public key in `./#{public_key_path}`
    3. If you wish to use a different name or location for your public key within the resources dir, 
       make sure you add `publish :public_key, 'folder/new_name.pem'` to the sparkle config in your Rakefile
        "
        return false
      end
      App.info "Sparkle", "Setup OK. After `rake build:release`, you can now run `rake sparkle:package`."
    end
setup_ok?() click to toggle source
# File lib/motion/project/setup.rb, line 4
def setup_ok?
  config_ok?
  certificates_ok?
end
sign_package() click to toggle source
# File lib/motion/project/package.rb, line 35
def sign_package
  package = "./#{sparkle_release_path}/#{zip_file}"
  @package_signature = `#{openssl} dgst -sha1 -binary < "#{package}" | #{openssl} dgst -dss1 -sign "#{private_key_path}" | #{openssl} enc -base64`
  @package_signature = @package_signature.strip
  App.info "Signature", "\"#{@package_signature}\""
end
sparkle_config_path() click to toggle source
# File lib/motion/project/sparkle.rb, line 145
def sparkle_config_path
  project_path + CONFIG_PATH
end
sparkle_distrib() click to toggle source
# File lib/motion/project/install.rb, line 4
def sparkle_distrib
  file_path = Pathname.new File.dirname(__FILE__)
  distrib_path = 'vendor/Sparkle.framework.zip'
  (file_path.parent.parent.parent + distrib_path).to_s
end
sparkle_path() click to toggle source
# File lib/motion/project/install.rb, line 10
def sparkle_path
  Pathname.new(vendor_path + 'Sparkle.framework')
end
sparkle_release_path() click to toggle source
# File lib/motion/project/sparkle.rb, line 141
def sparkle_release_path
  project_path + RELEASE_PATH
end
sparkle_zipball() click to toggle source
# File lib/motion/project/install.rb, line 14
def sparkle_zipball
  Pathname.new(vendor_path + 'Sparkle.framework.zip')
end
unzip() click to toggle source
# File lib/motion/project/install.rb, line 22
def unzip
  `unzip #{sparkle_zipball.to_s} -d #{vendor_path.to_s}`
  `rm #{sparkle_zipball}`
end
vendor_path() click to toggle source
# File lib/motion/project/sparkle.rb, line 133
def vendor_path
  @vendor_path ||= Pathname.new(project_path + 'vendor/')
end
verify_installation() click to toggle source
# File lib/motion/project/install.rb, line 45
    def verify_installation
      if installed?
        App.info "Sparkle", "Framework installed in #{sparkle_path.to_s}"
      else
        App.fail "Sparkle framework not correctly copied to #{sparkle_path.to_s}
Run `rake sparkle:install` manually or, if the problem persists, 
please explain your setup and problem as an issue on GitHub at:
https://github.com/webcracy/motion-sparkle/issues
"
      end
    end
version(vstring) click to toggle source
# File lib/motion/project/sparkle.rb, line 41
def version(vstring)
  @config.version = vstring.to_s
  @config.short_version = vstring.to_s
end
version_string() click to toggle source
# File lib/motion/project/sparkle.rb, line 46
def version_string
  "#{@config.version} (#{@config.short_version})"
end
zip_file() click to toggle source
# File lib/motion/project/sparkle.rb, line 174
def zip_file
  appcast.package_filename || "#{app_name}.zip"
end