Class: Motion::Project::Sparkle

Inherits:
Object
  • Object
show all
Defined in:
lib/motion/project/templates.rb,
lib/motion/project/version.rb,
lib/motion/project/sparkle.rb,
lib/motion/project/package.rb,
lib/motion/project/install.rb,
lib/motion/project/appcast.rb,
lib/motion/project/setup.rb

Defined Under Namespace

Classes: Appcast

Constant Summary collapse

TEMPLATE_PATHS =
[
  File.expand_path(File.join(__FILE__, '../appcast'))
]
VERSION =
'0.0.6'
SPARKLE_ROOT =
"sparkle"
CONFIG_PATH =
"#{SPARKLE_ROOT}/config"
RELEASE_PATH =
"#{SPARKLE_ROOT}/release"

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Sparkle

Returns a new instance of Sparkle.



8
9
10
11
12
# File 'lib/motion/project/sparkle.rb', line 8

def initialize(config)
  @config = config
  publish :public_key, 'dsa_pub.pem'
  install_and_embed
end

Instance Method Details

#add_to_gitignoreObject

File manipulation and certificates



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 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_templatesObject



11
12
13
14
15
16
17
18
19
# 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_pathObject



162
163
164
# File 'lib/motion/project/sparkle.rb', line 162

def app_bundle_path
  Pathname.new @config.app_bundle_raw('MacOSX')
end

#app_fileObject



178
179
180
# File 'lib/motion/project/sparkle.rb', line 178

def app_file
  "#{app_name}.app"
end

#app_nameObject



170
171
172
# File 'lib/motion/project/sparkle.rb', line 170

def app_name
  File.basename(app_bundle_path, '.app')
end

#app_release_pathObject



166
167
168
# File 'lib/motion/project/sparkle.rb', line 166

def app_release_path
  app_bundle_path.parent.to_s
end

#appcastObject



14
15
16
# File 'lib/motion/project/sparkle.rb', line 14

def appcast
  @appcast ||= Appcast.new
end

#appcast_xmlObject



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 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) ⇒ Boolean

Returns:

  • (Boolean)


40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 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_urlObject



15
16
17
18
19
20
21
# 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_urlObject



23
24
25
26
27
28
29
30
# 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_keyObject



32
33
34
35
36
37
38
# 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?Boolean

Returns:

  • (Boolean)


9
10
11
12
13
# 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) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
# 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_zipballObject



18
19
20
# File 'lib/motion/project/install.rb', line 18

def copy_zipball
  `cp #{sparkle_distrib} #{sparkle_zipball}`
end

#create_appcastObject



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 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_folderObject



81
82
83
# 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_folderObject



85
86
87
# 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_notesObject



4
5
6
7
8
9
10
11
12
13
14
# 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_folderObject



76
77
78
79
# File 'lib/motion/project/sparkle.rb', line 76

def create_sparkle_folder
  create_config_folder
  create_release_folder
end

#create_zip_fileObject



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 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_pathObject



149
150
151
# File 'lib/motion/project/sparkle.rb', line 149

def dsa_param_path
  sparkle_config_path + "dsaparam.pem"
end

#embedObject



36
37
38
# File 'lib/motion/project/install.rb', line 36

def embed
  @config.embedded_frameworks << sparkle_path
end

#feed_url(url) ⇒ Object



50
51
52
# File 'lib/motion/project/sparkle.rb', line 50

def feed_url(url)
  @config.info_plist['SUFeedURL'] = url
end

#generate_keysObject



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# 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_keyObject



119
120
121
# 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_pathObject



137
138
139
# File 'lib/motion/project/sparkle.rb', line 137

def gitignore_path
  project_path + ".gitignore"
end

#installObject



31
32
33
34
# File 'lib/motion/project/install.rb', line 31

def install
  copy_zipball
  unzip
end

#install_and_embedObject



40
41
42
43
# File 'lib/motion/project/install.rb', line 40

def install_and_embed
  install unless installed?
  embed
end

#installed?Boolean

Returns:

  • (Boolean)


27
28
29
# File 'lib/motion/project/install.rb', line 27

def installed?
  File.directory?(sparkle_path)
end

#opensslObject

A few helpers



125
126
127
# File 'lib/motion/project/sparkle.rb', line 125

def openssl
  "/usr/bin/openssl"
end

#packageObject



4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 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_pathObject



153
154
155
# File 'lib/motion/project/sparkle.rb', line 153

def private_key_path
  sparkle_config_path + "dsa_priv.pem"
end

#project_pathObject



129
130
131
# 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) ⇒ Object



54
55
56
# 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_pathObject



157
158
159
160
# 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) ⇒ Object Also known as: release



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 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

#release_notes_contentObject



76
77
78
79
80
81
82
# 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_pathObject



68
69
70
# File 'lib/motion/project/appcast.rb', line 68

def release_notes_content_path
  sparkle_config_path + "release_notes.content.html"
end

#release_notes_htmlObject



84
85
86
# File 'lib/motion/project/appcast.rb', line 84

def release_notes_html
  release_notes_content
end

#release_notes_pathObject



72
73
74
# File 'lib/motion/project/appcast.rb', line 72

def release_notes_path
  sparkle_release_path + appcast.notes_filename.to_s
end

#release_notes_template_pathObject



64
65
66
# File 'lib/motion/project/appcast.rb', line 64

def release_notes_template_path
  sparkle_config_path + "release_notes.template.erb"
end

#setupObject



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# 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?Boolean

Returns:

  • (Boolean)


4
5
6
7
# File 'lib/motion/project/setup.rb', line 4

def setup_ok?
  config_ok?
  certificates_ok?
end

#sign_packageObject



35
36
37
38
39
40
# 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_pathObject



145
146
147
# File 'lib/motion/project/sparkle.rb', line 145

def sparkle_config_path
  project_path + CONFIG_PATH
end

#sparkle_distribObject



4
5
6
7
8
# 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_pathObject



10
11
12
# File 'lib/motion/project/install.rb', line 10

def sparkle_path
  Pathname.new(vendor_path + 'Sparkle.framework')
end

#sparkle_release_pathObject



141
142
143
# File 'lib/motion/project/sparkle.rb', line 141

def sparkle_release_path
  project_path + RELEASE_PATH
end

#sparkle_zipballObject



14
15
16
# File 'lib/motion/project/install.rb', line 14

def sparkle_zipball
  Pathname.new(vendor_path + 'Sparkle.framework.zip')
end

#unzipObject



22
23
24
25
# 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_pathObject



133
134
135
# File 'lib/motion/project/sparkle.rb', line 133

def vendor_path
  @vendor_path ||= Pathname.new(project_path + 'vendor/')
end

#verify_installationObject



45
46
47
48
49
50
51
52
53
54
55
# 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) ⇒ Object



41
42
43
44
# File 'lib/motion/project/sparkle.rb', line 41

def version(vstring)
  @config.version = vstring.to_s
  @config.short_version = vstring.to_s
end

#version_stringObject



46
47
48
# File 'lib/motion/project/sparkle.rb', line 46

def version_string
  "#{@config.version} (#{@config.short_version})"
end

#zip_fileObject



174
175
176
# File 'lib/motion/project/sparkle.rb', line 174

def zip_file
  appcast.package_filename || "#{app_name}.zip"
end