# # # Author:: Mark Harrison-Ball # Date:: 20 Februray 2013 (AP3) # Purpose: # Will read through the tracking docs and extract approved data and bugnumbers # WIll then updat ethe meta data file used by the game to display if something has been approved # Will also generate a email report sent to producrion # Is deiged to run as a automated task checking perforce for checked in file # #----------------------------------------------------------------------------- # Uses #----------------------------------------------------------------------------- #include RSG::Base::Logging #include RSG::Base::Logging::Universal require 'RSG.Base.dll' require 'RSG.Base.Configuration.dll' require 'RSG.Base.Windows.dll' require 'RSG.SourceControl.Perforce' require 'RSG.Metadata' # Core include RSG::Base::Configuration include RSG::Base::Logging include RSG::Base::Logging::Universal include RSG::Base::OS include RSG::SourceControl::Perforce include RSG::Metadata require 'mscorlib' require 'System.Core' require 'System.Xml' require 'win32ole' include System::Collections::Generic include System::IO include System::Diagnostics include System::Xml include System::Runtime::InteropServices include System::Net include System::Net::Sockets include System::Net::Mail require 'pipeline/os/options' #require 'pipeline/util/email' include Pipeline DEBUG = false #----------------------------------------------------------------------------- # Constants #----------------------------------------------------------------------------- OPTIONS = [ LongOption::new( 'frequency', LongOption::ArgType.Required, 'f', 'frequency to check perforce for submits(required).' ) ] #EMAIL = 'mark.harrison-ball@rockstargames.com' EMAIL = 'AutoApprovedCutsceneUpdater@rockstargames.com' AUTHOR = ['mark.harrison-ball@rockstargames.com'] #SERVER = 'rsgldnexg1.rockstar.t2.corp' @server = 'rsgldnexg1.rockstar.t2.corp' #SMTPSERVER.Port = 25 PRODUCTION_EMAIL = ['dermot.bailie@rockstarnorth.com', 'tina.chen@rockstargames.com', 'sam.henman@rockstargames.com', 'francesca.howard@rockstarnorth.com', 'eric.smith@rockstarsandiego.com', 'sean.letts@rockstarsandiego.com', 'mark.harrison-ball@rockstargames.com'] =begin PRODUCTION_EMAIL = ['mark.harrison-ball@rockstargames.com', 'mark.harrison-ball@rockstargames.com'] =end DOF_COUNT = 0 LIGHTING_COUNT = 0 FACIAL_COUNT = 0 ANIMATION_COUNT = 0 CAMERA_COUNT = 0 # Total entries CUTLIST_COUNT = 0 FACIALLIST_COUNT = 0 #----------------------------------------------------------------------------- # Functions #----------------------------------------------------------------------------- #CUTSCENEMETA = "X:/gta5/build/dev/common/non_final/cutscene/ApprovedCutsceneList.meta" =begin Perfore functions =end # #-------------------------------------------------------- #def send_email( server, port, subject, body, from, to, reply_to = '', # from_alias = '', to_alias = '', reply_to_alias = '' ) def send_email(from, to_list, subject, message) #port= 25 #server= 'rsgnycexg04.rockstar.t2.corp' #subject='test' #body= 'test' #from= 'mark.harrison-ball@rockstargames.com' #to= 'matt.tempest@rockstarlondon.com' #to = 'mark.harrison-ball@rockstargames.com' begin Mail = System::Net::Mail::MailMessage.new Smptserver = System::Net::Mail::SmtpClient.new(@server) Smptserver.Port = 25 MailAddress = System::Net::Mail::MailAddress.new(from) Mail.From = MailAddress to_list.each do |to| Mail.To.Add(to) end Mail.IsBodyHtml = true Mail.Subject = subject Mail.Body = message Smptserver.Send(Mail); rescue => msg puts(msg) end end #--------------------------------------------------------------------------------------------------------------- # REPORT GENERTATION #--------------------------------------------------------------------------------------------------------------- # Convert bool to symbol #--------------------------------------------------- def returnHtmlBoolSymbol(bFlag) htmlString = "" if bFlag.class == String.class htmlString = "#{bFlag}" elsif bFlag.class == true.class htmlString = "ü" elsif bFlag.class == false.class htmlString = "û" end htmlString end def returnHtmlUrlLink(iValue) htmlString = "-" if not iValue == "-" bNumber = iValue.to_i htmlString = "#{bNumber}" end htmlString end # Create HTML HEader #---------------------------------------------- def HtmlHeader() @msgBody << "
Cutscene Tracking Changes > ApprovedCutsceneList.meta
" @msgBody << "
Date #{Time.now}


" @msgBody << "

Report generated from

//depot/gta5/docs/production/GTAV_CAMERA_TRACKING.xlsm
//depot/gta5/docs/production/Animation/V_Facial_Animation_Tracking.xlsm
changes CL#:#{@lastCL} to #{@currentCL}

" end def HtmlSummary() width = (800/100.0) @msgBody << "
Cutscene Overall Summary
" # Camera Approved Percentage cam_width = ((100.0/CUTLIST_COUNT)*CAMERA_COUNT) * width @msgBody << "
Camera Approved #{((100.0/CUTLIST_COUNT)*CAMERA_COUNT).round}%
" # DOF Approved Percentage dof_width = ((100.0/CUTLIST_COUNT)*DOF_COUNT) * width @msgBody << "
DOF Approved #{((100.0/CUTLIST_COUNT)*DOF_COUNT).round}%
" # Lighting Approved Percentage lig_width = ((100.0/CUTLIST_COUNT)*LIGHTING_COUNT) * width @msgBody << "
Lighting Approved (2nd pass) #{((100.0/CUTLIST_COUNT)*LIGHTING_COUNT).round}%
" # Animation Approved Percentage anm_width = ((100.0/CUTLIST_COUNT)*ANIMATION_COUNT) * width @msgBody << "
Animation Approved #{((100.0/CUTLIST_COUNT)*ANIMATION_COUNT).round}%
" # Facial Approved Percentage fce_width = ((100.0/FACIALLIST_COUNT)*FACIAL_COUNT) * width @msgBody << "
Facial Approved #{((100.0/FACIALLIST_COUNT)*FACIAL_COUNT).round}%

" end # Create Table Info #---------------------------------------------- def generateBaseTable(statusType,headerName, color) @msgBody << " " @emailLog.each do |key, value | # loop through scene Name attributes scenename = key hasMultipleStatus = false # We need to find out how many values are set to true, if more than 1 then we sett key and loop else index = 0 value.each do |attr | if attr.has_value?(statusType) index+=1 end end # we have multiple approved values set the same if index > 1 hasMultipleStatus = true end # We loop each list of attr value.each do |attr | if attr.has_value?(statusType) approvename = attr[:ApprovedName] status = attr[:Status] bugnumbers = attr[:Bug] @msgBody << "" if bugnumbers.size > 1 (1..bugnumbers.size-1).each do |n| @msgBody << "" end end if hasMultipleStatus == true scenename = "" hasMultipleStatus == false end end # SPACER end if index > 0 @msgBody << "" end end @msgBody << "

#{headerName}

Cutscene Name "" Status Bug Number
#{scenename} #{approvename} #{returnHtmlBoolSymbol(status)} #{returnHtmlUrlLink(bugnumbers[0])}
#{returnHtmlUrlLink(bugnumbers[n])}

" end def closeHtmlHeader() @msgBody << "
Generated by //depot/gta5/tools/wildwest/script/Ruby/ApprovedCutsceneUpdater/CutsceneApprovedUpdater.rb
" @msgBody << "
Author: Mark.Harrison-Ball@rockstargames.com
" end def generateEmailReport() HtmlHeader() HtmlSummary() generateBaseTable(true,"Approved Changes",'#556B2F') generateBaseTable(false,"Not Approved Changes",'B22222') generateBaseTable("new", "Name Changes",'#FFA500') closeHtmlHeader() if DEBUG aFile = File.new("x:/temp/test.html", "w") aFile.write(@msgBody) aFile.close end return @msgBody end #--------------------------------------------------------------------------------------------------------------- # #--------------------------------------------------------------------------------------------------------------- # Return Changelist objects from a set of Change records. #--------------------------------------------------------- def get_changelists_from_changes( log, p4, change_records ) changelists = [] change_records.each do |change_record| changelist = Changelist::Create( p4, change_record['change'].to_i ) changelists << changelist[0] end changelists end # Return the last submitted changelist number in perforce #-------------------------------------------------------- def get_current_changelistnumber(p4, log) change_records = p4.Run( 'changes', true, '-m', 1.to_s ) changes = get_changelists_from_changes( log, p4, change_records ) changes[0].Number end # #--------------------------------------------------------------------------------- def parse_p4_review(p4, depotpath, log) log.message( "Scanning for changes since last Changelist = #{@currentCL}.....") g_Status = 'submitted' bRequireCutsceneUpdate = false change_records = p4.Run( 'changes', true, '-s', g_Status, depotpath+'@'+@currentCL.to_s+',#head' ) changes = get_changelists_from_changes( log, p4, change_records ) changes.each do |change| log.message( "Changelist: #{change.Number} by #{change.Username}" ) if change.Number > @currentCL @currentCL = (change.Number+1) log.message( "Last Changelist = #{@currentCL}") end change.Files.each do |filename| file_name = File.basename(filename).downcase if file_name == 'gtav_camera_tracking.xlsm' or file_name == 'v_facial_animation_aracking.xlsm' @p4_infodescription += "\nChangelist: #{change.Number} File: #{file_name} by #{change.Username}" bRequireCutsceneUpdate = true end log.Message("\t#{filename}" ) end end bRequireCutsceneUpdate end # Revert if unchanged and tehn submit #--------------------------------------------------------------- def submit_p4_file(p4, file, changelist, log) # revert if unchnaged log.message( "Reverting unchanged files for changelist ##{changelist.Number.to_s}") reverted = p4.Run( 'revert', true, '-a', '-c', changelist.Number.to_s) if (reverted.count == 0) # means file was NOT reverted log.message( "Submitting changelist ##{changelist.Number.to_s}") reportmsg = generateEmailReport() if DEBUG send_email(@email, AUTHOR, 'Auto Approved Cutscene Updated', reportmsg) else p4.Run( 'submit', true, '-c', changelist.Number.to_s ) send_email(@email, PRODUCTION_EMAIL, 'Auto Approved Cutscene Updated', reportmsg) end else log.message( "Deleting empty changelist ##{changelist.Number.to_s}") changelist.delete end end #--------------------------------------------------- # Generic Excel Parser # Input: log / not nessecary # i: filename # i: rowkey - a column index to look up and the value to seach for # i: excelcolumnindexlist - List of column xcel names to collect #--------------------------------------------------- #def ParseExcelDoc(excelfilename, rowkey, excelcolumnindexlist, log) def ParseExcelDoc(excelfilename, rowkey, log) # Checks break if rowkey.length == 0 excel = WIN32OLE::new('excel.Application') workbook = excel.Workbooks.Open(excelfilename) worksheet = workbook.Worksheets(1) worksheet.Select excel['Visible'] = false #make visible, set to false to make invisible rows = worksheet.UsedRange.Rows.Count; cols = worksheet.UsedRange.Columns.Count; aArray = Array.new() eArray = Array.new() eSubArray = Array.new() excelStatus = 0 if rowkey[:key] key_name = rowkey[:subkey] end if rowkey[:subkey] subkey_name = rowkey[:subkey] end #aArray = Array.new() # We want to go trhough each row, if we have a key then read the rwo with the key count = 0 rows.times do |count| key = rowkey[:row] +(count+1).to_s masterLookUpVal = worksheet.range(key).value if masterLookUpVal != nil and masterLookUpVal.is_a? String if masterLookUpVal.downcase == rowkey[:key] if eArray.size > 0 # if we had a previous array if eSubArray.size > 0 eArray << eSubArray end aArray << eArray eArray = Array.new() eSubArray = Array.new() end excelStatus = 1 elsif masterLookUpVal.downcase == rowkey[:subkey] # we say look fo rsub info excelStatus = 2 else excelStatus = 0 end else excelStatus = 0 end # Master Data if excelStatus == 1 rowkey[:vals].each do |indexVal| lookupColIndex = indexVal+(count+1).to_s() val_toAdd = worksheet.range(lookupColIndex).value #puts(val_toAdd) eArray << val_toAdd end end # Subdata if excelStatus == 2 rowkey[:subvals].each do |indexVal| lookupColIndex = indexVal+(count+1).to_s() val_toAdd = worksheet.range(lookupColIndex).value eSubArray << val_toAdd end end if count == rows.size-1 # Add last array if eArray.size > 0 # if we had a previous array if eSubArray.size > 0 eArray << eSubArray end aArray << eArray end end end workbook.Close(false) excel.Quit excel = nil #GC.start # Invoke return aArray end =begin masterLookUpVal = testarray[i][rowkey[:row]] datVal = worksheet.range(key).value if datVal != nil begin #puts(datVal) if rowkey[:keys].include?(datVal.downcase) if datVal.downcase == rowkey[1].downcase # We found a key # Read each Header value into List eArray = Array.new() excelcolumnindexlist.each {|colkey| lookupColIndex = colkey+(count+1).to_s() val = worksheet.range(lookupColIndex).value eArray <<(val) } aArray << eArray end rescue log.Message("Skipping Value #{datVal}") end end end =end #--------------------------------------------------- # Read our Tracking Doc # Ok I need to change this so we look up for the actual name than the key #--------------------------------------------------- def ReadTrackingDoc(log, doc) log.Message("Opening Doc #{doc}") #rowkey = ["B","cs"] # optional row Key #rowkey = {:row => "B", :keys =>["cs", "fbx"]} # optional row Key #excelcolumnindexlist = ["d", "u", "y", "z","az","ba"] rowkey = {:row => "b", :key => "cs", :vals => ["d", "t", "x", "y","be","bf","v"], :subrow => "b", :subkey => "fbx", :subvals => ["i"] } # 0|D = Scene Name # 1|2 T/U = Edit Approved # 2|X = bug number for DOF # 3|Y = Dof approved # 4|BE = bug number for lighting # 5|BF = LIghting approved (2st Pass) # 6|V = Animation #ParseExcelDoc(doc, rowkey, excelcolumnindexlist,log) ParseExcelDoc(doc, rowkey, log) end #--------------------------------------------------- # Read our Facial Doc #-------------------------------------------------- def ReadFacialDoc(log, doc) log.Message("Opening Doc #{doc}") #rowkey = {:row => "f", :keys =>["face"]} # optional row Key #excelcolumnindexlist = ["ak","ai", "c"] # 0 Scene Name # 1 SOFT Approved # 2 = bugnumber rowkey = {:row => "g", :key => "face", :vals => ["al","aj","c"] } #ParseExcelDoc(doc, rowkey, excelcolumnindexlist, log) ParseExcelDoc(doc, rowkey, log) end #--------------------------------------------------- # Convert value to Bool #--------------------------------------------------- def convertToBool(value, log, valName) log.Message("#{valName} = #{value}") if (value.class == System::DateTime) return true elsif (value == 1.0) return true elsif (value == 'Test (Verifying)') return true elsif (value == 'Closed (Fixed)') return true elsif (value == 'Dev (Fixed, Awaiting Build)') return true else return false end end #--------------------------------------------------- # Convert value to Bool #--------------------------------------------------- def convertDateToBool(value, log, valName) log.Message("#{valName} = #{value.to_s}") #log.Message("#{valName} = #{value.class}") #puts(value.class) if (value.class == System::DateTime) return true else return false end end =begin def LogValueHasChanged(scenename, cutlistentryValue, exceldataEntryValue, cutlistentry) if cutlistentryValue.value != bcutval2 if not @emailLog.include?('chees33e') end else @emailLog['cheese'] = ['cutscneParroved = true'] end end cutentry.value = bcutval2 end =end def valueBoolChanged(bInputA, bInputB) bHasChanged = false if bInputA != bInputB bHasChanged = true end bHasChanged end def updateMetaEntry(cutlistentry, approvedName, exceldataValue, log, excelbugnumbersArray = ['-']) scenename = cutlistentry["CutsceneName"].value bApprovedFlag = cutlistentry[approvedName].value bExcelFlag = convertToBool(exceldataValue, log, approvedName) if approvedName == "CameraApproved" and bExcelFlag CAMERA_COUNT += 1 elsif approvedName == "LightingApproved" and bExcelFlag LIGHTING_COUNT +=1 elsif approvedName == "FacialApproved" and bExcelFlag FACIAL_COUNT +=1 elsif approvedName == "AnimationApproved" and bExcelFlag ANIMATION_COUNT +=1 elsif approvedName == "DofApproved" and bExcelFlag DOF_COUNT += 1 end if valueBoolChanged(bApprovedFlag, bExcelFlag) cutlistentry[approvedName].value = bExcelFlag if not @emailLog.include?(scenename) @emailLog[scenename] = [{:ApprovedName => approvedName, :Status => bExcelFlag, :Bug => excelbugnumbersArray}] else @emailLog[scenename] << {:ApprovedName=> approvedName, :Status => bExcelFlag, :Bug => excelbugnumbersArray} end end end #------------------------------------ # #----------------------------------- # 0 D = Scene Name # 1|2 U|V = Edit Approved # 2 bug number for lighting # 3 Z =Dof approved # 4 bug number for lighting # 5 LIghting approved (2st Pass) def updateMetaData(scenename, exceldataEntry, cutlistentry, log) #updateMetaEntry(cutlistentry, "CutsceneApproved", exceldataEntry[?], log) #log.message("CameraApproved = #{exceldataEntry[1]}, DofApproved = #{exceldataEntry[3]}, bug = #{[exceldataEntry[2]].to_s}, LightingApproved = #{exceldataEntry[5].to_s}, bug = #{[exceldataEntry[4]].to_s}") #print(exceldataEntry+"\n") updateMetaEntry(cutlistentry, "AnimationApproved", exceldataEntry[6], log ) updateMetaEntry(cutlistentry, "CameraApproved", exceldataEntry[1], log, exceldataEntry.last ) updateMetaEntry(cutlistentry, "DofApproved", exceldataEntry[3], log, [exceldataEntry[2]]) updateMetaEntry(cutlistentry, "LightingApproved", exceldataEntry[5], log, [exceldataEntry[4]]) end # 0 = bugnumbers # 1 SOFT Approved # 2 Scene Name def updateFaceMetaData(scenename, exceldataEntry, cutlistentry, log) updateMetaEntry(cutlistentry, "FacialApproved", exceldataEntry[0] , log, exceldataEntry[1] ) end #------------------------------------------------------- # Add a new tunable to our metadata file #------------------------------------------------------- def AddCutsceneEntry(name, memberUtils, cutlist, metaLoad) member = memberUtils.FindStructMemberByName(metaLoad.Definition.Members, "ApprovalStatuses") structTunable = RSG::Metadata::Data::StructureTunable.new(member, nil) structTunable.Item("CutsceneName").value = name cutlist.Add(structTunable) return structTunable end def UpdateCutscene_osi(p4, branch, g_Log) excelTrackfilename = 'X:/gta5/docs/production/GTAV_CAMERA_TRACKING.xlsm' excelFacialfilename = 'X:/gta5/docs/production/Animation/V_Facial_Animation_Tracking.xlsm' metaCameraFile = 'X:/gta5/build/dev/common/non_final/cutscene/ApprovedCutsceneList.meta' definationsPath = 'x:/gta5/assets/metadata/definitions/' #------------------------------------ # Sync to required Files #args = ['-f', Path.Combine( dest_dir, '...' ) g_Log.Message('Found changes, syncing Files') args = ['-f', excelTrackfilename, excelFacialfilename, metaCameraFile, ( definationsPath+ '...' )] p4.Run( 'sync', System::Array[System::String].new(args) ) changelist_description = 'Auto Approved Cutscene Update Submit [AP3]' +@p4_infodescription changelist = p4.CreatePendingChangelist(changelist_description) main_args = ['-c', changelist.Number.to_s] args = main_args + [metaCameraFile] args = System::Array[System::String].new(args) ret = p4.Run('edit', args) #g_Log.Message(args) #_Log.Message(ret) DOF_COUNT = 0 LIGHTING_COUNT = 0 FACIAL_COUNT = 0 ANIMATION_COUNT = 0 CAMERA_COUNT = 0 # Total entries CUTLIST_COUNT = 0 FACIALLIST_COUNT = 0 begin #---------------------------------- trackingData = ReadTrackingDoc(g_Log, excelTrackfilename) facialData = ReadFacialDoc(g_Log, excelFacialfilename) structspath = [definationsPath] structPathNet = System::Array[System::String].new(structspath) structDict = RSG::Metadata::Model::StructureDictionary.new structDict.load(branch, structPathNet) # put in beign catch g_Log.Message("loaded Definitions sucessfully") metaLoad = RSG::Metadata::Model::MetaFile.new( metaCameraFile, structDict, true) # Static Def calsses TunableUtils = RSG::Metadata::Util::Tunable MemberUtils = RSG::Metadata::Util::Member # Main Dictionary cutlist = TunableUtils.FindFirstStuctureNamed("ApprovalStatuses", metaLoad.Members ) # Create a array of scene names so we can do quick lookup from the cutlist. metasceneNames = []#Hash.new cutlist.each {|colkey| metasceneNames << colkey["CutsceneName"].value } g_Log.message("======================================CutNames lookup = #{metasceneNames.length}") g_Log.Message('Created look up') #------------ Check data CUTLIST_COUNT = trackingData.length #------------------------------ trackingData.each{|thisdata| # Check if cutscene name exists, if does update lscenename = thisdata[0].downcase #g_Log.Message(metasceneNames[lsceneName]) if metasceneNames.include?(lscenename) #if metasceneNames.has_key?(lSceneName) g_Log.Message("Found Existing cutscene #{lscenename}") index = metasceneNames.index(lscenename) updateMetaData(lscenename, thisdata, cutlist[index], g_Log ) # If does not exist add new data else g_Log.Message("Creating New Entry for cutscene #{lscenename}") entry = AddCutsceneEntry(lscenename, MemberUtils, cutlist, metaLoad) updateMetaData(lscenename, thisdata, entry, g_Log) #@emailLog[lscenename] = ["","New entry has been added","-"] @emailLog[lscenename] = [{:ApprovedName => "New entry/Name Change", :Status => "new", :Bug => ["-"]}] end # don't really need to care about old data } #---------------------------------- # Rebuild our lookup table metasceneNames.clear #= []#Hash.new cutlist.each {|colkey| metasceneNames << colkey["CutsceneName"].value } g_Log.message("====================================CutNames lookup= #{metasceneNames.length}") # So we need to do some colection on the arrays to make sure all characters have got facical passses g_Log.message("Parsing Facial Doc") validfacialData = Hash.new facialData.each{|thisdata| sceneName = thisdata[0].downcase if not validfacialData.include?(sceneName) validfacialData[sceneName] = [thisdata[1], [thisdata[2]]] else if (thisdata[1].class != System::DateTime) validfacialData[sceneName][0] = 0.0 end validfacialData[sceneName][1] << thisdata[2] #add bugnumbers end } g_Log.Message("validfacialData = #{validfacialData.length} , facialData = #{facialData.length}") FACIALLIST_COUNT = validfacialData.length # now lets go theough our filtered down Hash validfacialData.each do |key, value| if metasceneNames.include?(key) g_Log.Message("Found Existing cutscene #{key}") index = metasceneNames.index(key) updateFaceMetaData(key, value, cutlist[index], g_Log) end end # Save the new metafile metaLoad.SerialiseAlt(metaCameraFile) # revert and submit submit_p4_file(p4, metaCameraFile, changelist, g_Log) g_Log.Message("Sucess!") rescue => msg puts(msg) g_Log.error("Crash Deteted ") send_email(EMAIL, AUTHOR, 'Auto Approved Cutscene Updater', msg) end end #----------------------------------------------------------------------------- # Entry-Point #----------------------------------------------------------------------------- if ( __FILE__ == $0 ) then g_Options = OS::Options::new( OPTIONS ) LogFactory.CreateApplicationConsoleLogTarget( ) g_Log = LogFactory.ApplicationLog begin if ( g_Options.is_enabled?( 'help' ) ) puts "#{__FILE__}" puts "Usage:" puts g_Options.usage() exit( 1 ) end g_Frequency = g_Options.get?( 'frequency' ) unless g_Options.get( 'frequency' ).nil? # Perforce initialisation. g_Config = RSG::Base::Configuration::ConfigFactory::CreateConfig( ) @email = g_Config.EmailAddress @server= g_Config.studios.this_studio.exchange_server # FUCKING MAKES NOT FUCKING DIFFERNCE!@ @server= 'rsgldnexg1.rockstar.t2.corp' branch = g_Config.Project.DefaultBranch p4 = g_Config.Project.SCMConnect() # Statics depotpath = '//depot/gta5/docs/production/...' @currentCL = get_current_changelistnumber(p4, g_Log) #@currentCL = 3778825 @lastCL = @currentCL # USed for debug test if DEBUG @currentCL = 3757695 end @currentCL = 3757695 send_email(EMAIL, AUTHOR, 'Auto Approved Cutscene Updater', 'Cutscene Approved Updater has started!') repeat = true while repeat # Globals @p4_infodescription = '' @emailLog = Hash.new @msgBody = "" # --------- bRunCutsceneUpdate = parse_p4_review(p4, depotpath, g_Log) if bRunCutsceneUpdate #puts("Updating cutscene") UpdateCutscene_osi(p4,branch, g_Log) end puts("Waiting.........") sleep (14400) # seconds opt[:delay] end rescue => msg g_Log.error("Crash Deteted ") g_Log.error(msg) if not DEBUG send_email(EMAIL, AUTHOR, 'Auto Approved Cutscene Updater', msg) end end end