# # alienbrain.rb # Alienbrain SCM Class # # David Muir # Greg Smith # # 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