Files
2025-09-29 00:52:08 +02:00

624 lines
19 KiB
Ruby
Executable File

#
# alienbrain.rb
# Alienbrain SCM Class
#
# David Muir <david.muir@rockstarnorth.com>
# Greg Smith <greg.smith@rockstarnorth.com>
#
# References:
# * http://www.ruby-doc.org/stdlib/libdoc/win32ole/rdoc/index.html
# * http://alienbrain.wiki.avid.com/index.php/FAQ:Customization:General:1
# * http://alienbrain.wiki.avid.com/index.php/FAQ:Customization:General:2
# * http://alienbrain.wiki.avid.com/index.php/FAQ:Customization:Script
# * http://steve.yegge.googlepages.com/scripting-windows-apps
#
# (local_to_workspace_path example)
# * http://forum.alienbrain.com/index.php?showtopic=1038&st=0
# (file list for get latest - **HACK** does search for new server files first)
# * http://forum.alienbrain.com/index.php?showtopic=1064
#
# Requirements:
# * Alienbrain COM/OLE/ActiveX API (i.e. Alienbrain Client installed)
#
require 'pipeline/log/log'
require 'pipeline/os/file'
require 'pipeline/os/path'
require 'win32ole'
require 'fileutils'
require "win32/registry"
module Pipeline
#
# == Description
#
# Alienbrain Command Error exception.
#
#
class AlienbrainCommandError < StandardError
end
#
# == Description
#
# Used to store the loaded project in an alienbrain instance
# to prevent unnecessary reloading
#
#
class AlienbrainProject
#---------------------------------------------------------------------
# Constants
#---------------------------------------------------------------------
# None
#---------------------------------------------------------------------
# Attributes
#---------------------------------------------------------------------
attr_reader :host, :project, :username, :password
#---------------------------------------------------------------------
# Methods
#---------------------------------------------------------------------
def initialize(host, project, user, pass)
@host = host
@project = project
@username = user
@password = pass
end
end
#
# == Description
#
# Alienbrain client class utilising the Alienbrain COM API.
#
# == Example Usage
#
#
class Alienbrain
#---------------------------------------------------------------------
# Methods
#---------------------------------------------------------------------
#
# == Description
#
# Class Constructor
#
#
# == Example Usage
#
# ab = SCM::Alienbrain.new()
#
#
def initialize(host, project, user, pass, localroot = '', show_dialog = true)
Alienbrain.log.debug( "initialize" )
begin
if (defined? @@ab) == nil then
@@ab = WIN32OLE.new( ALIENBRAIN_NAMESPACE_HELPER )
end
rescue AlienbrainCommandError
raise
end
load_project(host,project,user,pass,localroot,show_dialog)
end
#
# == Description
#
# get the alienbrain log object
#
def Alienbrain.log()
if @@log_created == false then
@@log_created = true
@@log = Log.new("alienbrain")
end
@@log
end
#
# == Description
#
# Given an absolute path this determines if it is
# within the defined root
#
def path_valid?( path )
newpath = path[@root.length ... path.length]
return true if path == (@root + newpath)
false
end
#
# == Description
#
# Translates from an absolute path the one
# relative to the workspace root (the path required by other
# functions in this class)
#
def get_relative_path( path )
path = OS::Path.normalise(path)
newpath = path[(@root.length + 1)... path.length]
checkpath = OS::Path.combine(@root,newpath)
return nil if path != checkpath
newpath = OS::Path.combine(@project,newpath)
newpath
end
#
# == Description
#
# Get alienbrains install path on the current machine
#
#
def Alienbrain.install_path()
hkey = Win32::Registry::HKEY_LOCAL_MACHINE
search = 'SOFTWARE\NXN\alienbrain'
value = ""
begin
existkey = hkey.open(search)
value = existkey['InstallDir']
existkey.close
rescue
end
value.gsub("\\","/").downcase()
end
#
# == Description
#
# Backs up and deletes all the installed custom scripts of an alienbrain installation
#
#
def Alienbrain.remove_custom()
p = Pipeline::Config.instance()
rootpath = install_path() + "/client/customizations/eventscripts/"
targetpath = (p.toolsbin + "/uninstall/ab/")
FileUtils.mkdir_p targetpath
Dir.open(rootpath) { |d|
d.each { |f|
OS::FileUtilsEx.move_file(rootpath + f,targetpath + f) if f.downcase != "custommenu.js"
}
}
end
#
# == Description
#
# Reinstates all the backed up custom scripts for an installation
#
#
def Alienbrain.reinstate_custom()
p = Pipeline::Config.instance()
rootpath = (p.toolsbin + "/uninstall/ab/")
targetpath = install_path() + "/client/customizations/eventscripts/"
if OS::FileEx.directory? rootpath then
Dir.open(rootpath) { |d|
d.each { |f|
OS::FileUtilsEx.move_file(rootpath + f,targetpath + f)
}
}
end
end
#
# == Description
#
# Determine whether files/folders exist in the Alienbrain repository.
#
# Returns true if the file/folder exists, false otherwise.
#
# == Example Usage
#
# ab = SCM::Alienbrain.new( 'Glenfiddich', 'david.muir', '', 'tools_release' )
# puts "Folder exists: " + ab.exists( 'tools_release/gta_bin' ).to_s
# puts "File exists: " + ab.exists( 'tools_release/gta_bin/gta3.att' ).to_s
#
#
def exists( path )
Alienbrain.log.debug("exists")
begin
param = @@ab.getproperty( (ALIENBRAIN_WORKSPACE_ROOT + '/' + path), 'NamespaceType' )
result = param.gsub( '\\', '/' ).slice( 0, 28 )
( result == (ALIENBRAIN_WORKSPACE_ROOT + '/DbItem/FileFolder') )
rescue
false
end
end
#
# == Description
#
# Checkout a specific file in the current project workspace.
#
# == Example Usage
#
# ab = SCM::Alienbrain.new( 'Glenfiddich', 'david.muir', '', 'tools_release' )
# ab.checkout( 'tools_release/gta_bin/gta3.att' )
#
#
def checkout( file, prompt_for_comment = true, comment = "" )
Alienbrain.log.debug("checkout")
begin
ab_ole_command( AB_CMD_CHECKOUT,
{
AB_PARAM_CHECKOUT_SHOWMAINDLG => prompt_for_comment ? 1 : 0,
AB_PARAM_CHECKOUT_COMMENT => comment
},
(ALIENBRAIN_WORKSPACE_ROOT + '/' + file) )
rescue
raise
end
end
#
# == Description
#
# Checkin a specific file in the current project workspace.
#
# Unfortunately this always prompts for a comment, there does not seem
# to be a way to programatically provide a comment or accept the
# current comment.
#
# == Example Usage
#
# ab = SCM::Alienbrain.new( 'Glenfiddich', 'david.muir', '', 'tools_release' )
# ab.checkin( 'tools_release/gta_bin/gta3.att' )
#
#
def checkin( file )
Alienbrain.log.debug("checkin")
begin
ab_ole_command( AB_CMD_CHECKIN, nil, (ALIENBRAIN_WORKSPACE_ROOT + '/' + file) )
rescue
raise
end
end
#
# Search the Alienbrain repository for files using a specific mode.
# See the AB_FLAG_SEARCH_* constants for the different modes.
#
# Paths returned are local absolute file paths.
#
# == Example Usage
#
#
def search( path, mode )
Alienbrain.log.debug( 'search' )
files = []
begin
# Do search to find server newer files...
params = Hash.new()
params[AB_PARAM_SEARCH_EXPRESSION] = ''
params[AB_PARAM_SEARCH_PATH] = "#{ALIENBRAIN_WORKSPACE_ROOT}/#{path}"
params[AB_PARAM_SEARCH_FLAGS] = mode
params[AB_PARAM_SEARCH_PATTERN] = ''
ab_ole_command( AB_CMD_SEARCH, params, ALIENBRAIN_SEARCH_RESULTS )
@@ab.SetSelection( ALIENBRAIN_SEARCH_RESULTS )
if ( 1 != @@ab.Selection.length ) then
# Error - selection of search results failed...
puts "Error:: selecting Search Results - file list will be incomplete or empty."
else
result = @@ab.getfirstchild( ALIENBRAIN_SEARCH_RESULTS )
while ( "" != result )
#namespace_path = @@ab.getproperty( ALIENBRAIN_SEARCH_RESULTS + "/" + result, 'ShortcutTarget' )
local_path = @@ab.getproperty( ALIENBRAIN_SEARCH_RESULTS + "/" + result, 'LocalPath' )
files << OS::Path.normalise( local_path )
result = @@ab.getnextchild( ALIENBRAIN_SEARCH_RESULTS, result )
end
end
rescue
raise
end
files
end
#
# == Description
#
# Get latest versions from Alienbrain server for a specified
# file/folder in the current project.
#
# == Example Usage
#
# ab = SCM::Alienbrain.new( 'Glenfiddich', 'david.muir', '', 'tools_release' )
# ab.get_latest( 'tools_release/gta_bin' )
#
#
def get_latest( path, show_dialog = true, overwrite_differences = false )
Alienbrain.log.debug("get_latest")
files = []
begin
# Do search to find server newer files...
files = search( path, AB_FLAG_SEARCH_NEWER_ON_SERVER )
files = files + search( path, AB_FLAG_SEARCH_ONLY_ON_SERVER )
files = files + search( path, AB_FLAG_SEARCH_NEWER_ON_CLIENT ) if overwrite_differences
# Do 'GetLatest'
params = Hash.new()
params[AB_PARAM_GETLATEST_SHOWMAINDLG] = show_dialog ? 1 : 0
params[AB_PARAM_GETLATEST_OVERWRITE_CHECKEDOUT] = overwrite_differences ? 2 : 0
params[AB_PARAM_GETLATEST_OVERWRITE_WRITABLE] = overwrite_differences ? 2 : 0
params[AB_PARAM_GETLATEST_OVERWRITE_CHANGEDREADONLY] = overwrite_differences ? 2 : 0
ab_ole_command( AB_CMD_GETLATEST, params, (ALIENBRAIN_WORKSPACE_ROOT + '/' + path) )
rescue
raise
end
files
end
#
# Return a full Alienbrain workspace path from a local machine path.
#
def local_to_workspace_path( local_path )
mapper = WIN32OLE.new( ALIENBRAIN_MAPPER )
mapper.MapPath( local_path )
end
#
# Return a local path from an Alienbrain workspace path.
#
def workspace_to_local_path( workspace_path )
local_path = @@ab.getproperty( workspace_path, 'LocalPath' )
OS::Path.normalise( local_path )
end
#---------------------------------------------------------------------
# Public Constants
#---------------------------------------------------------------------
AB_FLAG_SEARCH_NORMAL = 0x00
AB_FLAG_SEARCH_NEWER_ON_CLIENT = 0x01
AB_FLAG_SEARCH_NEWER_ON_SERVER = 0x02
AB_FLAG_SEARCH_ONLY_ON_CLIENT = 0x10
AB_FLAG_SEARCH_ONLY_ON_SERVER = 0x20
private
#---------------------------------------------------------------------
# Private Constants
#---------------------------------------------------------------------
ALIENBRAIN_NAMESPACE_HELPER = 'NxNNamespace.NxNNamespaceHelper'
ALIENBRAIN_PARAM = 'NxNXMLHelper.NxNXMLParameter'
ALIENBRAIN_MAPPER = 'NxNMapper.Mapper'
ALIENBRAIN_WORKSPACE_ROOT = '/Workspace'
ALIENBRAIN_SEARCH_RESULTS = "#{ALIENBRAIN_WORKSPACE_ROOT}/Search Results"
#---------------------------------------------------------------------
# Private Constants : Aliebrain Commands and Parameters
#---------------------------------------------------------------------
AB_CMD_LOADPROJECT = 'ProjectLoadEx'
AB_PARAM_LOADPROJECT_NAME = 'Name'
AB_PARAM_LOADPROJECT_USER = 'Username'
AB_PARAM_LOADPROJECT_PASS = 'Password'
AB_PARAM_LOADPROJECT_HOST = 'Hostname'
AB_PARAM_LOADPROJECT_SHOWMAINDLG = 'ShowDialog'
AB_CMD_GETLATEST = 'GetLatest'
AB_PARAM_GETLATEST_OVERWRITE_CHECKEDOUT = 'OverwriteCheckedOut'
AB_PARAM_GETLATEST_OVERWRITE_CHANGEDREADONLY = 'OverwriteChangedReadonly'
AB_PARAM_GETLATEST_OVERWRITE_WRITABLE = 'OverwriteWritable'
AB_PARAM_GETLATEST_SHOWMAINDLG = 'ShowMainDialog'
AB_CMD_CHECKOUT = 'Checkout'
AB_PARAM_CHECKOUT_SHOWMAINDLG = 'ShowMainDialog'
AB_PARAM_CHECKOUT_COMMENT = 'Comment'
AB_CMD_CHECKIN = 'Checkin'
AB_CMD_SEARCH = 'Shortcut_Search'
AB_PARAM_SEARCH_EXPRESSION = 'SearchExpression'
AB_PARAM_SEARCH_PATH = 'NamespacePath'
AB_PARAM_SEARCH_FLAGS = 'SearchFlags'
AB_PARAM_SEARCH_PATTERN = 'LocalFilePattern'
#---------------------------------------------------------------------
# Private Variables
#---------------------------------------------------------------------
@@log = nil
@@log_created = false
@@hosts = Hash.new()
private
#---------------------------------------------------------------------
# Private Methods
#---------------------------------------------------------------------
#
# == Description
#
#
# Connect to an Alienbrain server and project with specified user settings
#
# == Example Usage
#
# ab = SCM::Alienbrain.new()
# ab.load_project( 'Glenfiddich', 'tools_release', 'david.muir', '', )
#
#
def load_project(host, project, user, pass, localroot = '', show_dialog = true)
Alienbrain.log.debug("load_project")
@host = host
@project = project
@username = user
@password = pass
@root = localroot
connect = false
if @@hosts[host] == nil then
@@hosts[host] = Hash.new()
end
if @@hosts[host][project] == nil then
connect = true
elsif @@hosts[host][project].username != user or @@hosts[host][project].password != pass then
connect = true
end
if connect then
@@hosts[host][project] = AlienbrainProject.new(host,project,user,pass)
begin
ab_ole_command( AB_CMD_LOADPROJECT,
{
AB_PARAM_LOADPROJECT_HOST => host,
AB_PARAM_LOADPROJECT_NAME => project,
AB_PARAM_LOADPROJECT_USER => user,
AB_PARAM_LOADPROJECT_PASS => pass,
AB_PARAM_LOADPROJECT_SHOWMAINDLG => show_dialog ? 1 : 0
},
ALIENBRAIN_WORKSPACE_ROOT )
rescue AlienbrainCommandError
raise
end
end
end
#---------------------------------------------------------------------
# This method is a quick helper to dispatch Alienbrain COM commands.
#
# It packages up the command and parameters into an Alienbrain XML
# Parameter and then run's the command.
#---------------------------------------------------------------------
def ab_ole_command( cmd, params, workspace )
Alienbrain.log.debug("ab_ole_command")
Alienbrain.log.debug("cmd: #{cmd} on workspace #{workspace}")
begin
# Initialise AB XML Parameter
param = WIN32OLE.new( ALIENBRAIN_PARAM )
rescue
raise AlienbrainCommandError.new( "error connecting to alienbrain through ole" )
end
param.reset( )
param.setproperty( 'Command', cmd )
#Alienbrain.log.debug( "AB Command: #{cmd}" )
if ( nil != params ) then
params.each { |p, value|
Alienbrain.log.debug("param: #{p} #{value}")
param.setproperty( "ParamIn", p, value )
}
# params.each { |p, value|
# Alienbrain.log.debug( "\t#{p} => #{value}" )
# }
end
# Execute command
param.xml = @@ab.RunCommand( workspace, cmd, param.xml, false )
if ( 1 != param.WasSuccessful ) then
Alienbrain.log.error( "AB Command #{cmd} failed." )
errormsg = ""
errormsg = param.events.item(0).message if param.events.count > 0
Alienbrain.log.error("error: #{errormsg}")
raise AlienbrainCommandError.new(errormsg)
end
end
end
end # Pipeline module
# End of alienbrain.rb