trabajo-afectivo/app/models/package.rb

478 lines
13 KiB
Ruby
Raw Normal View History

2014-02-03 19:23:00 +00:00
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
require 'rexml/document'
class Package < ApplicationModel
@@root = Rails.root.to_s # rubocop:disable Style/ClassVars
2013-01-17 08:49:44 +00:00
# build package based on .szpm
# Package.build(
2016-01-21 22:25:05 +00:00
# file: 'package.szpm',
# root: '/path/to/src/extention/',
# output: '/path/to/package_location/'
2013-01-17 08:49:44 +00:00
# )
2012-12-27 14:41:28 +00:00
def self.build(data)
if data[:file]
2016-01-21 22:25:05 +00:00
xml = _read_file(data[:file], data[:root] || true)
package = _parse(xml)
2012-12-27 14:41:28 +00:00
elsif data[:string]
2016-01-21 22:25:05 +00:00
package = _parse(data[:string])
2012-12-27 14:41:28 +00:00
end
build_date = REXML::Element.new('build_date')
build_date.text = Time.zone.now.iso8601
build_host = REXML::Element.new('build_host')
2012-12-27 09:29:13 +00:00
build_host.text = Socket.gethostname
2016-01-21 22:25:05 +00:00
package.root.insert_after('//zpm/description', build_date)
package.root.insert_after('//zpm/description', build_host)
package.elements.each('zpm/filelist/file') do |element|
location = element.attributes['location']
2016-01-21 22:25:05 +00:00
content = _read_file(location, data[:root])
base64 = Base64.encode64(content)
element.text = base64
end
2012-12-29 14:33:40 +00:00
if data[:output]
2016-01-21 22:25:05 +00:00
location = "#{data[:output]}/#{package.elements['zpm/name'].text}-#{package.elements['zpm/version'].text}.zpm"
2014-10-26 12:17:00 +00:00
logger.info "NOTICE: writting package to '#{location}'"
2016-01-21 22:25:05 +00:00
file = File.new(location, 'wb')
file.write(package.to_s)
2012-12-29 14:33:40 +00:00
file.close
return true
end
package.to_s
end
2013-01-17 08:49:44 +00:00
# Package.auto_install
# install all packages located under auto_install/*.zpm
def self.auto_install
2016-01-21 22:25:05 +00:00
path = "#{@@root}/auto_install/"
return if !File.exist?(path)
data = []
2016-01-21 22:25:05 +00:00
Dir.foreach(path) do |entry|
2014-03-10 15:45:52 +00:00
if entry =~ /\.zpm/ && entry !~ /^\./
data.push entry
end
end
data.each {|file|
2016-01-21 22:25:05 +00:00
install(file: "#{path}/#{file}")
}
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
def self.unlink_all
# link files
2016-01-21 22:25:05 +00:00
Dir.glob("#{@@root}/**/*") do |entry|
if File.symlink?(entry)
2014-10-26 12:17:00 +00:00
logger.info "unlink: #{entry}"
2016-01-21 22:25:05 +00:00
File.delete(entry)
end
2013-03-19 15:39:31 +00:00
backup_file = entry + '.link_backup'
2016-01-21 22:25:05 +00:00
if File.exist?(backup_file)
2014-10-26 12:17:00 +00:00
logger.info "Restore backup file of #{backup_file} -> #{entry}."
2016-01-21 22:25:05 +00:00
File.rename(backup_file, entry)
2013-03-19 15:39:31 +00:00
end
end
end
# check if zpm is a package source repo
def self._package_base_dir?(package_base_dir)
package = false
2016-01-21 22:25:05 +00:00
Dir.glob(package_base_dir + '/*.szpm') do |entry|
package = entry.sub(%r{^.*/(.+?)\.szpm$}, '\1')
end
if package == false
fail "Can't link package, '#{package_base_dir}' is no package source directory!"
end
2014-10-26 12:17:00 +00:00
logger.debug package.inspect
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 = _package_base_dir?(package_base_dir)
# migration down
2016-01-21 22:25:05 +00:00
Package::Migration.migrate(package, 'reverse')
# link files
2016-01-21 22:25:05 +00:00
Dir.glob(package_base_dir + '/**/*') do |entry|
entry = entry.sub('//', '/')
file = entry
2016-01-21 22:25:05 +00:00
file = file.sub(/#{package_base_dir.to_s}/, '')
dest = @@root + '/' + file
2016-01-21 22:25:05 +00:00
if File.symlink?(dest.to_s)
logger.info "Unlink file: #{dest}"
2016-01-21 22:25:05 +00:00
File.delete(dest.to_s)
end
2013-03-19 15:39:31 +00:00
backup_file = dest.to_s + '.link_backup'
2016-01-21 22:25:05 +00:00
if File.exist?(backup_file)
logger.info "Restore backup file of #{backup_file} -> #{dest}."
2016-01-21 22:25:05 +00:00
File.rename(backup_file, dest.to_s)
2013-03-19 15:39:31 +00:00
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 = _package_base_dir?(package_base_dir)
# link files
2016-01-21 22:25:05 +00:00
Dir.glob(package_base_dir + '/**/*') do |entry|
entry = entry.sub('//', '/')
file = entry
2016-01-21 22:25:05 +00:00
file = file.sub(/#{package_base_dir.to_s}/, '')
file = file.sub(%r{^/}, '')
2013-02-02 18:52:31 +00:00
# ignore files
if file =~ /^README/
2014-10-26 12:17:00 +00:00
logger.info "NOTICE: Ignore #{file}"
2013-02-02 18:52:31 +00:00
next
end
# get new file destination
dest = @@root + '/' + file
2016-01-21 22:25:05 +00:00
if File.directory?(entry.to_s)
if !File.exist?(dest.to_s)
logger.info "Create dir: #{dest}"
2016-01-21 22:25:05 +00:00
FileUtils.mkdir_p(dest.to_s)
end
end
2016-01-21 22:25:05 +00:00
if File.file?(entry.to_s) && (File.file?(dest.to_s) && !File.symlink?(dest.to_s))
2013-03-19 15:39:31 +00:00
backup_file = dest.to_s + '.link_backup'
2016-01-21 22:25:05 +00:00
if File.exist?(backup_file)
fail "Can't link #{entry} -> #{dest}, destination and .link_backup already exists!"
2013-03-19 15:39:31 +00:00
else
logger.info "Create backup file of #{dest} -> #{backup_file}."
2016-01-21 22:25:05 +00:00
File.rename(dest.to_s, backup_file)
2013-03-19 15:39:31 +00:00
end
end
2016-01-21 22:25:05 +00:00
if File.file?(entry)
if File.symlink?(dest.to_s)
File.delete(dest.to_s)
end
logger.info "Link file: #{entry} -> #{dest}"
2016-01-21 22:25:05 +00:00
File.symlink(entry.to_s, dest.to_s)
end
end
# migration up
2016-01-21 22:25:05 +00:00
Package::Migration.migrate(package)
end
2016-01-21 22:25:05 +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]
2016-01-21 22:25:05 +00:00
xml = _read_file(data[:file], true)
package = _parse(xml)
2012-12-27 14:41:28 +00:00
elsif data[:string]
2016-01-21 22:25:05 +00:00
package = _parse(data[:string])
2012-12-27 14:41:28 +00:00
end
# package meta data
2012-12-27 14:41:28 +00:00
meta = {
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
2016-01-21 22:25:05 +00:00
package_db = Package.find_by(name: meta[:name])
if package_db
2013-03-19 12:12:22 +00:00
if !data[:reinstall]
2016-01-21 22:25:05 +00:00
if Gem::Version.new(package_db.version) == Gem::Version.new(meta[:version])
fail "Package '#{meta[:name]}-#{meta[:version]}' already installed!"
2013-03-19 12:12:22 +00:00
end
2016-01-21 22:25:05 +00:00
if Gem::Version.new(package_db.version) > Gem::Version.new(meta[:version])
fail "Newer version (#{package_db.version}) of package '#{meta[:name]}-#{meta[:version]}' already installed!"
2013-03-19 12:12:22 +00:00
end
end
2013-03-22 12:48:47 +00:00
# uninstall files of old package
uninstall(
name: package_db.name,
version: package_db.version,
migration_not_down: true,
2015-04-27 15:21:17 +00:00
)
end
# store package
2016-01-21 22:25:05 +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,
2016-01-21 22:25:05 +00:00
filename: "#{meta[:name]}-#{meta[:version]}.zpm",
preferences: {},
created_by_id: UserInfo.current_user_id || 1,
2013-03-19 12:12:22 +00:00
)
end
# write files
package.elements.each('zpm/filelist/file') do |element|
2012-12-27 14:41:28 +00:00
location = element.attributes['location']
permission = element.attributes['permission'] || '644'
2012-12-27 14:41:28 +00:00
base64 = element.text
content = Base64.decode64(base64)
content = _write_file(location, permission, content)
end
# update package state
record.state = 'installed'
record.save
# up migrations
2016-01-21 22:25:05 +00:00
Package::Migration.migrate(meta[:name])
# prebuild assets
true
end
2016-01-21 22:25:05 +00:00
# Package.reinstall(package_name)
2013-03-19 12:12:22 +00:00
def self.reinstall(package_name)
2016-01-21 22:25:05 +00:00
package = Package.find_by(name: package_name)
2013-05-10 21:48:29 +00:00
if !package
fail "No such package '#{package_name}'"
2013-05-10 21:48:29 +00:00
end
2013-03-19 12:12:22 +00:00
2016-01-21 22:25:05 +00:00
file = _get_bin(package.name, package.version)
install(string: file, reinstall: true)
2013-03-19 12:12:22 +00:00
end
2016-01-21 22:25:05 +00:00
# Package.uninstall(name: 'package', version: '0.1.1')
# Package.uninstall(string: zpm_as_string)
def self.uninstall(data)
2012-12-27 14:41:28 +00:00
if data[:string]
2016-01-21 22:25:05 +00:00
package = _parse(data[:string])
2012-12-27 14:41:28 +00:00
else
2016-01-21 22:25:05 +00:00
file = _get_bin(data[:name], data[:version])
package = _parse(file)
2012-12-27 14:41:28 +00:00
end
# package meta data
2012-12-27 14:41:28 +00:00
meta = {
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]
2016-01-21 22:25:05 +00:00
Package::Migration.migrate(meta[:name], 'reverse')
end
package.elements.each('zpm/filelist/file') do |element|
2012-12-27 14:41:28 +00:00
location = element.attributes['location']
permission = element.attributes['permission'] || '644'
2012-12-27 14:41:28 +00:00
base64 = element.text
content = Base64.decode64(base64)
content = _delete_file(location, permission, content)
end
# prebuild assets
# delete package
record = Package.find_by(
name: meta[:name],
version: meta[:version],
)
record.destroy
true
end
def self._parse(xml)
2014-10-26 12:17:00 +00:00
logger.debug xml.inspect
begin
2016-01-21 22:25:05 +00:00
package = REXML::Document.new(xml)
rescue => e
2015-05-04 18:58:28 +00:00
logger.error 'ERROR: ' + e.inspect
return
end
2014-10-26 12:17:00 +00:00
logger.debug package.inspect
package
end
2016-01-21 22:25:05 +00:00
def self._get_bin(name, version)
package = Package.find_by(
name: name,
version: version,
)
if !package
fail "No such package '#{name}' version '#{version}'"
end
list = Store.list(
object: 'Package',
o_id: package.id,
)
# find file
2013-05-10 21:48:29 +00:00
if !list || !list.first
fail "No such file in storage list #{name} #{version}"
2013-05-10 21:48:29 +00:00
end
2014-04-28 07:44:36 +00:00
if !list.first.content
fail "No such file in storage #{name} #{version}"
2013-05-10 21:48:29 +00:00
end
2014-04-28 07:44:36 +00:00
list.first.content
end
def self._read_file(file, fullpath = false)
location = if fullpath == false
@@root + '/' + file
elsif fullpath == true
file
else
fullpath + '/' + file
end
begin
2016-01-21 22:25:05 +00:00
data = File.open(location, 'rb')
contents = data.read
rescue => e
raise 'ERROR: ' + e.inspect
end
2014-04-28 07:44:36 +00:00
contents
end
def self._write_file(file, permission, data)
2016-01-21 22:25:05 +00:00
location = "#{@@root}/#{file}"
# rename existing file
2016-01-21 22:25:05 +00:00
if File.exist?(location)
backup_location = location + '.save'
2014-10-26 12:17:00 +00:00
logger.info "NOTICE: backup old file '#{location}' to #{backup_location}"
2016-01-21 22:25:05 +00:00
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|
2016-01-21 22:25:05 +00:00
tmp_path = "#{tmp_path}/#{directories[count]}"
}
next if tmp_path == ''
next if File.exist?(tmp_path)
Dir.mkdir(tmp_path, 0755)
}
# install file
begin
2014-10-26 12:17:00 +00:00
logger.info "NOTICE: install '#{location}' (#{permission})"
2016-01-21 22:25:05 +00:00
file = File.new(location, 'wb')
file.write(data)
file.close
2016-01-21 22:25:05 +00:00
File.chmod(permission.to_i(8), location)
rescue => e
raise 'ERROR: ' + e.inspect
end
2014-04-28 07:44:36 +00:00
true
end
def self._delete_file(file, _permission, _data)
2016-01-21 22:25:05 +00:00
location = "#{@@root}/#{file}"
# install file
2014-10-26 12:17:00 +00:00
logger.info "NOTICE: uninstall '#{location}'"
2016-01-21 22:25:05 +00:00
if File.exist?(location)
File.delete(location)
end
# rename existing file
backup_location = location + '.save'
2016-01-21 22:25:05 +00:00
if File.exist?(backup_location)
2014-10-26 12:17:00 +00:00
logger.info "NOTICE: restore old file '#{backup_location}' to #{location}"
2016-01-21 22:25:05 +00:00
File.rename(backup_location, location)
end
true
end
class Migration < ApplicationModel
@@root = Rails.root.to_s # rubocop:disable Style/ClassVars
2016-01-21 22:25:05 +00:00
def self.migrate(package, direction = 'normal')
location = "#{@@root}/db/addon/#{package.underscore}"
2016-01-21 22:25:05 +00:00
return true if !File.exist?(location)
migrations_done = Package::Migration.where(name: package.underscore)
# 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
fail "Invalid package migration '#{migration}'"
end
# down
if direction == 'reverse'
2016-01-21 22:25:05 +00:00
done = Package::Migration.find_by(name: package.underscore, version: version)
next if !done
2014-10-26 12:17:00 +00:00
logger.info "NOTICE: down package migration '#{migration}'"
load "#{location}/#{migration}"
classname = name.camelcase
Kernel.const_get(classname).down
2016-01-21 22:25:05 +00:00
record = Package::Migration.find_by(name: package.underscore, version: version)
if record
record.destroy
end
# up
else
2016-01-21 22:25:05 +00:00
done = Package::Migration.find_by(name: package.underscore, version: version)
next if done
2014-10-26 12:17:00 +00:00
logger.info "NOTICE: up package migration '#{migration}'"
load "#{location}/#{migration}"
classname = name.camelcase
Kernel.const_get(classname).up
2016-01-21 22:25:05 +00:00
Package::Migration.create(name: package.underscore, version: version)
end
}
end
end
end