homebrew-cask-versions/lib/cask.rb
Roland Walker bf99c6b0a3 Store metadata with installed Casks.
Step 2: Snapshot the Cask file used at install-time, using
the previously-merged metadata directory support.

Using a simple filesystem-is-a-database approach, we set up a
`.metadata` directory for each installed Cask in which we can
record any information, starting with a copy of the Cask
definition which was used at install-time.

This should be useful for various cases such as:
- a fallback when the Cask was renamed/removed.  We
  currently cannot recover any uninstall info in that
  scenario
- installation of multiple versions of the same software

There might be a smarter way to discover the source filename
for the Cask through introspection or some deep Ruby voodoo.
All I could come up with was to pass the filename in on the
constructor, which seems perfectly reasonable if voodoo is
not available.  The existing code was taking the title as an
argument to the constructor, which is dispensable.

This PR contains no code to actually make use of the metadata,
but only takes care of the relevant book-keeping: creation,
destruction, as well as organization of metadata according to
software version number and timestamp.
2014-12-10 18:24:43 -05:00

167 lines
5 KiB
Ruby

HOMEBREW_CACHE_CASKS = HOMEBREW_CACHE.join('Casks')
class Cask; end
require 'download_strategy'
require 'cask/artifact'
require 'cask/audit'
require 'cask/auditor'
require 'cask/without_source'
require 'cask/checkable'
require 'cask/cli'
require 'cask/caveats'
require 'cask/container'
require 'cask/download'
require 'cask/download_strategy'
require 'cask/dsl'
require 'cask/exceptions'
require 'cask/fetcher'
require 'cask/installer'
require 'cask/locations'
require 'cask/options'
require 'cask/pkg'
require 'cask/pretty_listing'
require 'cask/qualified_token'
require 'cask/scopes'
require 'cask/source'
require 'cask/staged'
require 'cask/system_command'
require 'cask/underscore_supporting_uri'
require 'cask/url'
require 'cask/url_checker'
require 'cask/utils'
require 'cask/version'
require 'plist/parser'
class Cask
include Cask::DSL
include Cask::Locations
include Cask::Scopes
include Cask::Options
include Cask::Utils
def self.init
# todo: Creating directories should be deferred until needed.
# Currently this fire and even asks for sudo password
# if a first-time user simply runs "brew cask --help".
odebug 'Creating directories'
HOMEBREW_CACHE.mkpath unless HOMEBREW_CACHE.exist?
HOMEBREW_CACHE_CASKS.mkpath unless HOMEBREW_CACHE_CASKS.exist?
unless caskroom.exist?
ohai "We need to make Caskroom for the first time at #{caskroom}"
ohai "We'll set permissions properly so we won't need sudo in the future"
current_user = Etc.getpwuid(Process.euid).name
if caskroom.parent.writable?
system '/bin/mkdir', '--', caskroom
else
toplevel_dir = caskroom
toplevel_dir = toplevel_dir.parent until toplevel_dir.parent.root?
unless toplevel_dir.directory?
# If a toplevel dir such as '/opt' must be created, enforce standard permissions.
# sudo in system is rude.
system '/usr/bin/sudo', '--', '/bin/mkdir', '--', toplevel_dir
system '/usr/bin/sudo', '--', '/bin/chmod', '--', '0775', toplevel_dir
end
# sudo in system is rude.
system '/usr/bin/sudo', '--', '/bin/mkdir', '-p', '--', caskroom
unless caskroom.parent == toplevel_dir
system '/usr/bin/sudo', '--', '/usr/sbin/chown', '-R', '--', "#{current_user}:staff", caskroom.parent
end
end
end
end
def self.load(query)
odebug 'Loading Cask definitions'
cask = Cask::Source.for_query(query).load
cask.dumpcask
cask
end
def self.token
# todo removeme: prepending KlassPrefix is transitional as we move away from representing Casks as classes
self.name.sub(/^KlassPrefix/,'').gsub(/([a-zA-Z\d])([A-Z])/,'\1-\2').gsub(/([a-zA-Z\d])([A-Z])/,'\1-\2').downcase
end
def self.nowstamp_metadata_path(container_path)
@timenow ||= Time.now.gmtime
if container_path.respond_to?(:join)
precision = 3
timestamp = @timenow.strftime('%Y%m%d%H%M%S')
fraction = ("%.#{precision}f" % (@timenow.to_f - @timenow.to_i))[1..-1]
timestamp.concat(fraction)
container_path.join(timestamp)
end
end
attr_reader :token, :sourcefile_path
def initialize(sourcefile_path=nil)
@sourcefile_path = sourcefile_path
@token = self.class.token
end
def caskroom_path
self.class.caskroom.join(token)
end
# todo: move to staged.rb ?
def staged_path
cask_version = version ? version : :unknown
caskroom_path.join(cask_version.to_s)
end
def metadata_master_container_path
caskroom_path.join(self.class.metadata_subdir)
end
def metadata_versioned_container_path
cask_version = version ? version : :unknown
metadata_master_container_path.join(cask_version.to_s)
end
def metadata_path(timestamp=:latest, create=false)
return nil unless metadata_versioned_container_path.respond_to?(:join)
if create and timestamp == :latest
raise CaskError.new('Cannot create metadata path when timestamp is :latest')
end
if timestamp == :latest
path = Pathname.glob(metadata_versioned_container_path.join('*')).sort.last
elsif timestamp == :now
path = self.class.nowstamp_metadata_path(metadata_versioned_container_path)
else
path = metadata_versioned_container_path.join(timestamp)
end
if create
odebug "Creating metadata directory #{path}"
FileUtils.mkdir_p path
end
path
end
def metadata_subdir(leaf, timestamp=:latest, create=false)
if create and timestamp == :latest
raise CaskError.new('Cannot create metadata subdir when timestamp is :latest')
end
unless leaf.respond_to?(:length) and leaf.length > 0
raise CaskError.new('Cannot create metadata subdir for empty leaf')
end
parent = metadata_path(timestamp, create)
return nil unless parent.respond_to?(:join)
subdir = parent.join(leaf)
if create
odebug "Creating metadata subdirectory #{subdir}"
FileUtils.mkdir_p subdir
end
subdir
end
def installed?
staged_path.exist?
end
def to_s
@token
end
end