624 lines
19 KiB
Ruby
Executable File
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
|