2012-12-26 22:02:16 +00:00
|
|
|
require 'rexml/document'
|
|
|
|
class Package < ApplicationModel
|
|
|
|
@@root = Rails.root.to_s
|
|
|
|
|
2013-01-17 08:49:44 +00:00
|
|
|
# build package based on .szpm
|
|
|
|
# Package.build(
|
|
|
|
# :file => 'package.szpm',
|
|
|
|
# :root => '/path/to/src/extention/',
|
|
|
|
# :output => '/path/to/package_location/'
|
|
|
|
# )
|
2012-12-27 14:41:28 +00:00
|
|
|
def self.build(data)
|
|
|
|
|
|
|
|
if data[:file]
|
2012-12-27 23:52:13 +00:00
|
|
|
xml = self._read_file( data[:file], data[:root] || true )
|
2012-12-27 14:41:28 +00:00
|
|
|
package = self._parse(xml)
|
|
|
|
elsif data[:string]
|
|
|
|
package = self._parse( data[:string] )
|
|
|
|
end
|
2012-12-26 22:02:16 +00:00
|
|
|
|
2012-12-27 09:29:13 +00:00
|
|
|
build_date = REXML::Element.new("build_date")
|
|
|
|
build_date.text = Time.now.utc.iso8601
|
|
|
|
build_host = REXML::Element.new("build_host")
|
|
|
|
build_host.text = Socket.gethostname
|
|
|
|
|
|
|
|
package.root.insert_after( '//zpm/description', build_date )
|
|
|
|
package.root.insert_after( '//zpm/description', build_host )
|
2012-12-26 22:02:16 +00:00
|
|
|
package.elements.each('zpm/filelist/file') do |element|
|
|
|
|
location = element.attributes['location']
|
2012-12-27 23:52:13 +00:00
|
|
|
content = self._read_file( location, data[:root] )
|
2012-12-26 22:02:16 +00:00
|
|
|
base64 = Base64.encode64(content)
|
|
|
|
element.text = base64
|
|
|
|
end
|
2012-12-29 14:33:40 +00:00
|
|
|
if data[:output]
|
|
|
|
location = data[:output] + '/' + package.elements["zpm/name"].text + '-' + package.elements["zpm/version"].text + '.zpm'
|
|
|
|
puts "NOTICE: writting package to '#{location}'"
|
|
|
|
file = File.new( location, 'wb' )
|
|
|
|
file.write( package.to_s )
|
|
|
|
file.close
|
|
|
|
return true
|
|
|
|
end
|
2012-12-26 22:02:16 +00:00
|
|
|
return package.to_s
|
|
|
|
end
|
|
|
|
|
2013-01-17 08:49:44 +00:00
|
|
|
# Package.auto_install
|
|
|
|
# install all packages located under auto_install/*.zpm
|
2013-01-07 08:32:48 +00:00
|
|
|
def self.auto_install
|
|
|
|
path = @@root + '/auto_install/'
|
|
|
|
return if ! File.exist?( path )
|
|
|
|
data = []
|
|
|
|
Dir.foreach( path ) do |entry|
|
|
|
|
if entry =~ /\.zpm/
|
|
|
|
data.push entry
|
|
|
|
end
|
|
|
|
end
|
|
|
|
data.each {|file|
|
|
|
|
self.install( :file => path + '/' + file )
|
|
|
|
}
|
|
|
|
return data
|
|
|
|
end
|
|
|
|
|
2013-01-17 08:49:44 +00:00
|
|
|
# Package.unlink_all
|
|
|
|
# remove all linked files in application
|
|
|
|
# note: will not take down package migrations, use Package.unlink instead
|
2013-01-16 20:09:27 +00:00
|
|
|
def self.unlink_all
|
|
|
|
# link files
|
|
|
|
Dir.glob( @@root + '/**/*' ) do |entry|
|
|
|
|
if File.symlink?( entry)
|
|
|
|
puts "unlink: #{entry}"
|
|
|
|
File.delete( entry )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# check if zpm is a package source repo
|
|
|
|
def self._package_base_dir?(package_base_dir)
|
|
|
|
package = false
|
|
|
|
Dir.glob( package_base_dir + '/*.szpm') do |entry|
|
|
|
|
package = entry.sub( /^.*\/(.+?)\.szpm$/, '\1')
|
|
|
|
end
|
|
|
|
if package == false
|
|
|
|
raise "Can't link package, '#{package_base_dir}' is no package source directory!"
|
|
|
|
end
|
|
|
|
puts package.inspect
|
|
|
|
return package
|
|
|
|
end
|
|
|
|
|
|
|
|
# Package.unlink('/path/to/src/extention')
|
|
|
|
# execute migration down + unlink files
|
|
|
|
def self.unlink(package_base_dir)
|
|
|
|
|
|
|
|
# check if zpm is a package source repo
|
|
|
|
package = self._package_base_dir?(package_base_dir)
|
|
|
|
|
|
|
|
# migration down
|
|
|
|
Package::Migration.migrate( package, 'reverse' )
|
|
|
|
|
|
|
|
# link files
|
|
|
|
Dir.glob( package_base_dir + '/**/*' ) do |entry|
|
|
|
|
entry = entry.sub( '//', '/' )
|
|
|
|
file = entry
|
|
|
|
file = file.sub( /#{package_base_dir.to_s}/, '' )
|
|
|
|
dest = @@root + '/' + file
|
|
|
|
|
|
|
|
if File.symlink?( dest.to_s )
|
|
|
|
puts "Unlink file: #{dest.to_s}"
|
|
|
|
File.delete( dest.to_s )
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Package.link('/path/to/src/extention')
|
|
|
|
# link files + execute migration up
|
|
|
|
def self.link(package_base_dir)
|
|
|
|
|
|
|
|
# check if zpm is a package source repo
|
|
|
|
package = self._package_base_dir?(package_base_dir)
|
|
|
|
|
|
|
|
# link files
|
|
|
|
Dir.glob( package_base_dir + '/**/*' ) do |entry|
|
|
|
|
entry = entry.sub( '//', '/' )
|
|
|
|
file = entry
|
|
|
|
file = file.sub( /#{package_base_dir.to_s}/, '' )
|
2013-02-02 18:52:31 +00:00
|
|
|
file = file.sub( /^\//, '' )
|
|
|
|
|
|
|
|
# ignore files
|
|
|
|
if file =~ /^README/
|
|
|
|
puts "NOTICE: Ignore #{file}"
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
# get new file destination
|
2013-01-16 20:09:27 +00:00
|
|
|
dest = @@root + '/' + file
|
|
|
|
|
|
|
|
if File.directory?( entry.to_s )
|
|
|
|
if !File.exists?( dest.to_s )
|
|
|
|
puts "Create dir: #{dest.to_s}"
|
|
|
|
FileUtils.mkdir_p( dest.to_s )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if File.file?( entry.to_s ) && ( File.file?( dest.to_s ) && !File.symlink?( dest.to_s ) )
|
|
|
|
raise "Can't link #{entry.to_s} -> #{dest.to_s}, destination already exists!"
|
|
|
|
end
|
|
|
|
|
|
|
|
if File.file?( entry )
|
|
|
|
if File.symlink?( dest.to_s )
|
|
|
|
File.delete( dest.to_s )
|
|
|
|
end
|
|
|
|
puts "Link file: #{entry.to_s} -> #{dest.to_s}"
|
|
|
|
File.symlink( entry.to_s, dest.to_s )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# migration up
|
|
|
|
Package::Migration.migrate( package )
|
|
|
|
end
|
|
|
|
|
2013-01-17 08:49:44 +00:00
|
|
|
# Package.install( :file => '/path/to/package.zpm' )
|
|
|
|
# Package.install( :string => zpm_as_string )
|
2012-12-27 14:41:28 +00:00
|
|
|
def self.install(data)
|
|
|
|
if data[:file]
|
|
|
|
xml = self._read_file( data[:file], true )
|
|
|
|
package = self._parse(xml)
|
|
|
|
elsif data[:string]
|
|
|
|
package = self._parse( data[:string] )
|
|
|
|
end
|
2012-12-26 22:02:16 +00:00
|
|
|
|
|
|
|
# package meta data
|
2012-12-27 14:41:28 +00:00
|
|
|
meta = {
|
2012-12-26 22:02:16 +00:00
|
|
|
:name => package.elements["zpm/name"].text,
|
|
|
|
:version => package.elements["zpm/version"].text,
|
|
|
|
:vendor => package.elements["zpm/vendor"].text,
|
|
|
|
:state => 'uninstalled',
|
|
|
|
:created_by_id => 1,
|
|
|
|
:updated_by_id => 1,
|
|
|
|
}
|
|
|
|
|
|
|
|
# verify if package can get installed
|
2012-12-27 14:41:28 +00:00
|
|
|
package_db = Package.where( :name => meta[:name] ).first
|
2012-12-26 22:02:16 +00:00
|
|
|
if package_db
|
2013-03-19 12:12:22 +00:00
|
|
|
if !data[:reinstall]
|
|
|
|
if Gem::Version.new( package_db.version ) == Gem::Version.new( meta[:version] )
|
|
|
|
raise "Package '#{meta[:name]}-#{meta[:version]}' already installed!"
|
|
|
|
end
|
|
|
|
if Gem::Version.new( package_db.version ) > Gem::Version.new( meta[:version] )
|
|
|
|
raise "Newer version (#{package_db.version}) of package '#{meta[:name]}-#{meta[:version]}' already installed!"
|
|
|
|
end
|
2012-12-26 22:02:16 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# uninstall old package
|
2012-12-27 14:41:28 +00:00
|
|
|
self.uninstall({
|
|
|
|
:name => package_db.name,
|
|
|
|
:version => package_db.version,
|
|
|
|
:migration_not_down => true,
|
|
|
|
})
|
2012-12-26 22:02:16 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# store package
|
2012-12-27 14:41:28 +00:00
|
|
|
record = Package.create( meta )
|
2013-03-19 12:12:22 +00:00
|
|
|
if !data[:reinstall]
|
|
|
|
Store.add(
|
|
|
|
:object => 'Package',
|
|
|
|
:o_id => record.id,
|
|
|
|
:data => package.to_s,
|
|
|
|
:filename => meta[:name] + '-' + meta[:version] + '.zpm',
|
|
|
|
:preferences => {},
|
|
|
|
)
|
|
|
|
end
|
2012-12-26 22:02:16 +00:00
|
|
|
|
|
|
|
# write files
|
|
|
|
package.elements.each('zpm/filelist/file') do |element|
|
2012-12-27 14:41:28 +00:00
|
|
|
location = element.attributes['location']
|
2012-12-26 22:02:16 +00:00
|
|
|
permission = element.attributes['permission'] || '644'
|
2012-12-27 14:41:28 +00:00
|
|
|
base64 = element.text
|
|
|
|
content = Base64.decode64(base64)
|
|
|
|
content = self._write_file(location, permission, content)
|
2012-12-26 22:02:16 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# update package state
|
|
|
|
record.state = 'installed'
|
|
|
|
record.save
|
|
|
|
|
2013-02-01 17:43:37 +00:00
|
|
|
# reload new files
|
|
|
|
Package.reload_classes
|
|
|
|
|
2012-12-26 22:02:16 +00:00
|
|
|
# up migrations
|
2012-12-27 14:41:28 +00:00
|
|
|
Package::Migration.migrate( meta[:name] )
|
2012-12-26 22:02:16 +00:00
|
|
|
|
|
|
|
# prebuild assets
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2013-03-19 12:12:22 +00:00
|
|
|
# Package.reinstall( package_name )
|
|
|
|
def self.reinstall(package_name)
|
|
|
|
package = Package.where( :name => package_name ).first
|
|
|
|
return if !package
|
|
|
|
|
|
|
|
file = self._get_bin( package.name, package.version )
|
|
|
|
return if !file
|
|
|
|
self.install( :string => file, :reinstall => true )
|
|
|
|
end
|
|
|
|
|
2013-01-17 08:49:44 +00:00
|
|
|
# Package.uninstall( :name => 'package', :version => '0.1.1' )
|
|
|
|
# Package.uninstall( :string => zpm_as_string )
|
2012-12-27 14:41:28 +00:00
|
|
|
def self.uninstall( data )
|
2012-12-26 22:02:16 +00:00
|
|
|
|
2012-12-27 14:41:28 +00:00
|
|
|
if data[:string]
|
|
|
|
package = self._parse( data[:string] )
|
|
|
|
else
|
|
|
|
file = self._get_bin( data[:name], data[:version] )
|
|
|
|
package = self._parse(file)
|
|
|
|
end
|
2012-12-26 22:02:16 +00:00
|
|
|
|
|
|
|
# package meta data
|
2012-12-27 14:41:28 +00:00
|
|
|
meta = {
|
2012-12-26 22:02:16 +00:00
|
|
|
:name => package.elements["zpm/name"].text,
|
|
|
|
:version => package.elements["zpm/version"].text,
|
|
|
|
}
|
|
|
|
|
|
|
|
# down migrations
|
2012-12-27 14:41:28 +00:00
|
|
|
if !data[:migration_not_down]
|
|
|
|
Package::Migration.migrate( meta[:name], 'reverse' )
|
2012-12-26 22:02:16 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
package.elements.each('zpm/filelist/file') do |element|
|
2012-12-27 14:41:28 +00:00
|
|
|
location = element.attributes['location']
|
2012-12-26 22:02:16 +00:00
|
|
|
permission = element.attributes['permission'] || '644'
|
2012-12-27 14:41:28 +00:00
|
|
|
base64 = element.text
|
|
|
|
content = Base64.decode64(base64)
|
|
|
|
content = self._delete_file(location, permission, content)
|
2012-12-26 22:02:16 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# prebuild assets
|
|
|
|
|
2013-02-01 19:58:53 +00:00
|
|
|
# reload new files
|
|
|
|
Package.reload_classes
|
|
|
|
|
2012-12-26 22:02:16 +00:00
|
|
|
# delete package
|
|
|
|
record = Package.where(
|
2012-12-27 14:41:28 +00:00
|
|
|
:name => meta[:name],
|
|
|
|
:version => meta[:version],
|
2012-12-26 22:02:16 +00:00
|
|
|
).first
|
|
|
|
record.destroy
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2013-02-01 17:43:37 +00:00
|
|
|
# reload .rb files in case they have changed
|
|
|
|
def self.reload_classes
|
|
|
|
['app', 'lib'].each {|dir|
|
|
|
|
Dir.glob( Rails.root.join( dir + '/**/*') ).each {|entry|
|
|
|
|
if entry =~ /\.rb$/
|
|
|
|
load entry
|
|
|
|
end
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2012-12-26 22:02:16 +00:00
|
|
|
def self._parse(xml)
|
|
|
|
# puts xml.inspect
|
|
|
|
begin
|
|
|
|
package = REXML::Document.new( xml )
|
|
|
|
rescue => e
|
|
|
|
puts 'ERROR: ' + e.inspect
|
|
|
|
return
|
|
|
|
end
|
|
|
|
# puts package.inspect
|
|
|
|
return package
|
|
|
|
end
|
|
|
|
|
|
|
|
def self._get_bin( name, version )
|
|
|
|
package = Package.where(
|
|
|
|
:name => name,
|
|
|
|
:version => version,
|
|
|
|
).first
|
|
|
|
if !package
|
|
|
|
raise "No such package '#{name}' version '#{version}'"
|
|
|
|
end
|
|
|
|
list = Store.list(
|
|
|
|
:object => 'Package',
|
|
|
|
:o_id => package.id,
|
|
|
|
)
|
|
|
|
|
|
|
|
# find file
|
|
|
|
return if !list
|
|
|
|
list.first.store_file.data
|
|
|
|
end
|
|
|
|
|
|
|
|
def self._read_file(file, fullpath = false)
|
2012-12-27 23:52:13 +00:00
|
|
|
if fullpath == false
|
2012-12-26 22:02:16 +00:00
|
|
|
location = @@root + '/' + file
|
2012-12-27 23:52:13 +00:00
|
|
|
elsif fullpath == true
|
2012-12-26 22:02:16 +00:00
|
|
|
location = file
|
2012-12-27 23:52:13 +00:00
|
|
|
else
|
|
|
|
location = fullpath + '/' + file
|
2012-12-26 22:02:16 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
begin
|
|
|
|
data = File.open( location, 'rb' )
|
|
|
|
contents = data.read
|
|
|
|
rescue => e
|
|
|
|
raise 'ERROR: ' + e.inspect
|
|
|
|
end
|
|
|
|
return contents
|
|
|
|
end
|
|
|
|
|
|
|
|
def self._write_file(file, permission, data)
|
|
|
|
location = @@root + '/' + file
|
|
|
|
|
|
|
|
# rename existing file
|
|
|
|
if File.exist?( location )
|
|
|
|
backup_location = location + '.save'
|
|
|
|
puts "NOTICE: backup old file '#{location}' to #{backup_location}"
|
|
|
|
File.rename( location, backup_location )
|
|
|
|
end
|
|
|
|
|
|
|
|
# check if directories need to be created
|
|
|
|
directories = location.split '/'
|
|
|
|
(0..(directories.length-2) ).each {|position|
|
|
|
|
tmp_path = ''
|
|
|
|
(1..position).each {|count|
|
|
|
|
tmp_path = tmp_path + '/' + directories[count].to_s
|
|
|
|
}
|
|
|
|
if tmp_path != ''
|
|
|
|
if !File.exist?(tmp_path)
|
|
|
|
Dir.mkdir( tmp_path, 0755)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
# install file
|
|
|
|
begin
|
|
|
|
puts "NOTICE: install '#{location}' (#{permission})"
|
|
|
|
file = File.new( location, 'wb' )
|
|
|
|
file.write( data )
|
|
|
|
file.close
|
|
|
|
File.chmod( permission.to_i(8), location )
|
|
|
|
rescue => e
|
|
|
|
raise 'ERROR: ' + e.inspect
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
def self._delete_file(file, permission, data)
|
|
|
|
location = @@root + '/' + file
|
|
|
|
|
|
|
|
# install file
|
|
|
|
puts "NOTICE: uninstall '#{location}'"
|
2013-03-05 16:16:33 +00:00
|
|
|
if File.exist?( location )
|
|
|
|
File.delete( location )
|
|
|
|
end
|
2012-12-26 22:02:16 +00:00
|
|
|
|
|
|
|
# rename existing file
|
|
|
|
backup_location = location + '.save'
|
|
|
|
if File.exist?( backup_location )
|
|
|
|
puts "NOTICE: restore old file '#{backup_location}' to #{location}"
|
|
|
|
File.rename( backup_location, location )
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
class Migration < ApplicationModel
|
|
|
|
@@root = Rails.root.to_s
|
|
|
|
|
2012-12-27 14:41:28 +00:00
|
|
|
def self.migrate( package, direction = 'normal' )
|
2012-12-26 22:02:16 +00:00
|
|
|
location = @@root + '/db/addon/' + package.underscore
|
|
|
|
|
|
|
|
return true if !File.exists?( location )
|
2012-12-28 00:19:56 +00:00
|
|
|
migrations_done = Package::Migration.where( :name => package.underscore )
|
2012-12-26 22:02:16 +00:00
|
|
|
|
|
|
|
# get existing migrations
|
|
|
|
migrations_existing = []
|
|
|
|
Dir.foreach(location) {|entry|
|
|
|
|
next if entry == '.'
|
|
|
|
next if entry == '..'
|
|
|
|
migrations_existing.push entry
|
|
|
|
}
|
|
|
|
|
|
|
|
# up
|
|
|
|
migrations_existing = migrations_existing.sort
|
|
|
|
|
|
|
|
# down
|
|
|
|
if direction == 'reverse'
|
|
|
|
migrations_existing = migrations_existing.reverse
|
|
|
|
end
|
|
|
|
|
|
|
|
migrations_existing.each {|migration|
|
|
|
|
next if migration !~ /\.rb$/
|
|
|
|
version = nil
|
|
|
|
name = nil
|
|
|
|
if migration =~ /^(.+?)_(.*)\.rb$/
|
|
|
|
version = $1
|
|
|
|
name = $2
|
|
|
|
end
|
|
|
|
if !version || !name
|
|
|
|
raise "Invalid package migration '#{migration}'"
|
|
|
|
end
|
|
|
|
|
|
|
|
# down
|
|
|
|
if direction == 'reverse'
|
2012-12-28 00:19:56 +00:00
|
|
|
done = Package::Migration.where( :name => package.underscore, :version => version ).first
|
2012-12-26 22:02:16 +00:00
|
|
|
next if !done
|
|
|
|
puts "NOTICE: down package migration '#{migration}'"
|
|
|
|
load "#{location}/#{migration}"
|
|
|
|
classname = name.camelcase
|
|
|
|
Kernel.const_get(classname).down
|
2012-12-28 00:19:56 +00:00
|
|
|
record = Package::Migration.where( :name => package.underscore, :version => version ).first
|
2012-12-26 22:02:16 +00:00
|
|
|
if record
|
|
|
|
record.destroy
|
|
|
|
end
|
|
|
|
|
|
|
|
# up
|
|
|
|
else
|
2012-12-28 00:19:56 +00:00
|
|
|
done = Package::Migration.where( :name => package.underscore, :version => version ).first
|
2012-12-26 22:02:16 +00:00
|
|
|
next if done
|
|
|
|
puts "NOTICE: up package migration '#{migration}'"
|
|
|
|
load "#{location}/#{migration}"
|
|
|
|
classname = name.camelcase
|
|
|
|
Kernel.const_get(classname).up
|
2012-12-28 00:19:56 +00:00
|
|
|
Package::Migration.create( :name => package.underscore, :version => version )
|
2012-12-26 22:02:16 +00:00
|
|
|
end
|
2013-02-01 17:43:37 +00:00
|
|
|
|
|
|
|
# reload new files
|
|
|
|
Package.reload_classes
|
2012-12-26 22:02:16 +00:00
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|