# # File:: monitor_label.rb # Description:: Utility class to monitor a perforce folder # for label updates. # # Author:: David Muir # Author:: Derek Ward # Date:: 16 June 2009 # # == Example Usage # # path = '//depot/jimmy/build/independent/...' # monitor = SCM::MonitorLabel.new( path, server, client, user, '', 'monitor.xml' ) # # # Start continual polling loop, until monitor.stop called, this loop could # # run in its own thread. # monitor.poll() do | files, skipped| # end # # # Poll SCM repository once, if you are monitoring multiple repositories # # or paths and act on them you likely want this version (e.g. Codebuilder) # monitorLabel.poll_once() do | files, skipped| # end # #----------------------------------------------------------------------------- # Uses #----------------------------------------------------------------------------- require 'pipeline/config/projects' require 'pipeline/resourcing/convert' require 'pipeline/scm/perforce' require 'pipeline/scm/perforce_helper' require 'pipeline/util/thread' require 'rexml/document' # REXML Save Fix require 'pipeline/util/rexml_write_fix' #----------------------------------------------------------------------------- # Entry-Point #----------------------------------------------------------------------------- module Pipeline module SCM # # == Description # The MonitorLabelStatus class is an object created by the MonitorLabel class that # stores the current date and time the label was last successfully synced. # class MonitorLabelStatus attr_reader :filename attr_accessor :label_sync_date_time NODE_ROOT = 'local' ATTR_SYNC_DATE_TIME = 'sync_date_time' def initialize( filename ) @filename = filename reload( ) end # # Return the REXML::Element for the status XML file. This allows # builder apps to add additional data into the XML file. Be very # careful you do not overwrite the root node's changelist attribute, # otherwise bad things will happen. # # Maybe in the future we will present a better interface. # def xmlroot( ) @xmldoc.root end # # Force a reload of the XML disk file. # def reload( ) if ( ::File::exists?( @filename ) ) then File.open( @filename ) do |file| @xmldoc = REXML::Document.new( file ) @label_sync_date_time = @xmldoc.root.attributes[ATTR_SYNC_DATE_TIME] end else @label_sync_date_time = 'not synced' @xmldoc = REXML::Document.new( ) @xmldoc << REXML::XMLDecl.new @xmldoc.add_element( NODE_ROOT ) @xmldoc.root.add_attribute( ATTR_SYNC_DATE_TIME, @label_sync_date_time.to_s ) save( ) end end # # Save the current status to XML disk file. # def save( ) # Update XML Document @xmldoc.root.attributes[ATTR_SYNC_DATE_TIME] = @label_sync_date_time.to_s # Flush to disk File.open( @filename, "w+" ) do |file| @xmldoc.write( file, 4 ) end end end class MonitorLabel DEF_POLL_TIME = 60.0 attr_reader :p4 attr_reader :running attr_reader :poll_time attr_reader :root_folder attr_reader :status attr_reader :label #--------------------------------------------------------------------- # Virtual Attributes #--------------------------------------------------------------------- def current_label_date_time( ) return ( @status.label_sync_date_time) end # # MonitorLabel constructor, specifying Perforce server details including # root folder to watch (note: append '/...' for recursive watch). # def initialize( sc_root, # P4 path to monitor sc_server, # P4 server:port string sc_clientspec, # P4 clientspec name to use sc_username, # P4 username config_filename, label, poll_time = DEF_POLL_TIME ) @status = MonitorLabelStatus.new( config_filename ) @poll_time = poll_time @c = Pipeline::Config.instance @root_folder = sc_root @label = label @p4 = SCM::Perforce.new @p4.exception_level = P4::RAISE_ERRORS @p4.user = sc_username @p4.client = sc_clientspec @p4.port = sc_server MonitorLabel.log().debug( "Connecting to #{@p4.port}, #{@p4.client}..." ) @p4.connect MonitorLabel.log().debug( "Connected? #{@p4.connected?}" ) end # # Start the monitorLabel with an optional block, continually polling the # Perforce server for changes. The block takes four arguments: # Perforce object, change list ID, P4 sync output hash, and skipped bool. # def poll( &block ) @running = true while ( @running ) do self.poll_once( &block ) Kernel.sleep( @poll_time ) end end # # Start the monitorLabel with an optional block, only polling the Perforce # server once for the label. The block takes four arguments: # Perforce object, P4 sync output hash, and skipped bool. # def poll_once( &block ) begin @p4.connect( ) sync_to_label( &block ) @p4.disconnect( ) rescue Exception => ex puts "Exception in MonitorLabel.poll_once(): #{ex.message}" puts ex.backtrace.join( "\n" ) end end # # Stop the monitorLabel on its next iteration. This might not stop the # monitor immediately as it might be blocking in a Kernel.sleep(). # def stop( ) @running = false @p4.disconnect( ) if ( @p4.connected?() ) end #--------------------------------------------------------------------- # Class Methods #--------------------------------------------------------------------- # # Return a MonitorLabel instance from a P4 object. # def MonitorLabel::from_p4( p4, sc_root, config_filename, label, poll_time = DEF_POLL_TIME ) throw ArgumentError.new( "Invalid P4 object specified (#{p4.class})." ) \ unless ( p4.is_a?( P4 ) ) MonitorLabel.new( sc_root, p4.port, p4.client, p4.user, config_filename, label, poll_time ) end # # Return the MonitorLabel class logger object. # def MonitorLabel::log() @@log = Log.new( 'monitor_label' ) if @@log.nil? @@log end def sync_to_label( &block ) begin @p4.connect() unless ( @p4.connected?() ) MonitorLabel.log().debug( "Syncing to Label." ) update_to_label( &block ) rescue Exception => ex puts "Exception in MonitorLabel.sync_to_label(): #{ex.message}" puts ex.backtrace.join( "\n" ) end end # sync_to_label # # Have we actually got the correct revisions of the files in the label. # def is_synced_to_label( ) is_synced = true begin # get a list of files in the label, in an array of hashes with useful p4 file data keyed within. label_files = @p4.run_fstat("-Ol", "#{@root_folder}@#{@label}") label_files.each do |label_file| if (label_file.has_key?("haveRev") and label_file.has_key?("headRev")) then if label_file['haveRev'] != label_file['headRev'] then is_synced = false puts " File not synced : #{label_file['depotFile']} [#{label_file['haveRev']}/#{label_file['headRev']}] for label #{@label}" end end end rescue Exception => ex puts "Exception in is_synced_to_label #{ex.message}" end is_synced end # is_synced_to_label # # Does this label exist # def label_exists( ) begin label_out = @p4.run_labels("-e", "#{@label}") label_out.each { |lbl_out| return true if lbl_out.has_key?("label") and lbl_out["label"]==@label } rescue P4Exception => ex puts "P4 Exception: #{ex.message}" end false end # label_exists #--------------------------------------------------------------------- # Protected #--------------------------------------------------------------------- protected @@log = nil # # Sync our root folder to a specific label. # def update_to_label( ) MonitorLabel.log().debug( "update_to : #{@root_folder} @#{@label}" ) MonitorLabel.log().debug( "connected? #{@p4.connected?}" ) puts "Attempting Update to label #{@label}" label_synced_ok = false out = nil begin throw Exception.new("Label #{label} not found.") unless label_exists puts "p4 sync @root_folder@#{@label}" out = @p4.run_sync(@root_folder + "@#{@label}") puts "output : #{out.length}" out.each { |o| puts o.to_s } # you know I've just realised that really we should be using a dynamic label name rather than the same name due to # possible 'non thread safe' like issues with the use of this label here. thats a bit of work though. label_synced_ok = is_synced_to_label() puts "label_synced_ok #{label_synced_ok}" rescue Exception => ex puts "Label sync Exception: #{ex.message}" rescue P4Exception => ex puts "P4Exception: #{ex.message}" end something_synced = ( out.is_a?( Array ) and ( out.size > 0 ) ) if label_synced_ok and something_synced then @status.label_sync_date_time = Time.now @status.save( ) end yield( nil, out, label_synced_ok ) if ( block_given? and something_synced==true ) puts "End attempting Update to label something_synced=#{something_synced}" end end end # SCM module end # Pipeline module # monitorlabel.rb