hve_projects_service.rb #1

  • //
  • guest/
  • ptomiak/
  • hws/
  • source/
  • helix_web_services/
  • lib/
  • helix_versioning_engine/
  • hve_projects_service.rb
  • View
  • Commits
  • Open Download .zip Download (6 KB)
require 'hws_settings'
require 'projects/project_service'
require 'base64'

require 'hws_strings'

module HelixVersioningEngine
  # Define simple projects based on a single directory location in a Helix
  # Versioning Engine.
  #
  # This project service uses the HVE_PROJECTS_PATH setting to list available
  # projects on the particular server. If you only have one Helix Versioning
  # Engine, it's suitable to create this as a systemwide default. Otherwise,
  # you likely only want to specify this via headers.
  #
  # This will return everything as "helix versioning engine" project.
  class HVEProjectsService

    HVE_CONTENT_TYPE = 'application/vnd.perforce.project.hve.v1+json'

    HVE_ID = 'hveProject'

    # Rack environment.
    attr_accessor :env

    def initialize(env: nil)
      @env = env
    end

    # List HVE Projects as configured in the system.
    #
    # See the Appendix in the documentation for details on values.
    def list(details: false, extension: nil)
      return if extension and (extension != HVE_ID or extension != HVE_CONTENT_TYPE)

      project_dirs = list_project_names
      project_names = project_dirs.map { |d| File.basename(d) }

      if details
        project_names.map { |n| fetch_by_name(n) }
      else
        project_names.map { |n| encode_name(n) }
      end
    end

    def list_project_names
      pattern = "#{hve_projects_path}/*"
      results = p4.run_dirs(pattern)
      results.map { |r| r['dir'] }
    end

    # The ID is a URL encoded version of the directory name under
    # HVE_PROJECTS_PATH.
    #
    # This will unencode the ID and fetch by name.
    def fetch(id)
      name = unencode_name(id)
      fetch_by_name(name)
    end

    # Returns the project's "details" based on the project name.
    #
    # No validation is done to ensure this directory actually exists in the
    # system.
    def fetch_by_name(name)
      id = encode_name(name)

      {
          'id': id,
          'name': name,
          'server': server_uri_for_id(id),
          HVE_ID => {
              'depotPath': depot_path_for_name(name)
          }
      }
    end

    # Generate a new client that only contains the project mapping.
    #
    # The client name is a combination of user, project, and device. We prefix
    # it with "_hve" just for clarity.
    #
    # We do not host lock the client.
    #
    # @param project_id {String} Our encoded project name
    # @param device {String} A device ID, like a hostname
    # @param root {String} The `Root` value for the client parameter
    def create_client(project_id, device, root)
      client_name = "_hve_#{user}_#{project_id}_#{device}"

      client_spec = p4.fetch_client(client_name)

      client_spec._root = root;
      client_spec._host = nil;

      client_spec._options = 'allwrite noclobber nocompress unlocked nomodtime rmdir';

      project_name = unencode_name(project_id)

      client_spec._view = [
          %Q|"#{depot_path_for_name(project_name)}/..." "//#{client_name}/..."|
      ]

      results = p4.save_client(client_spec)
      puts "new client results: #{results}"

      client_name
    end

    # Find the latest submitted change for the project
    #
    # Use the 'p4 changes -m 1 -s submitted [depot path]'
    #
    # @param project_id [String] The encoded project ID
    def find_latest_change_for_project(project_id)
      project_name = unencode_name(project_id)
      depot_path = depot_path_for_name(project_name)

      results = p4.run_changes('-m', '1', '-s', 'submitted', "#{depot_path}/...")

      results.first['change'] unless results.empty?
    end

    # The HVE project 'changelist' is a pending changelist whose description is
    # `_hve_[user]_[project_id]`
    #
    # We use 'changes -l' to find the change to match potentially long project
    # names.
    def find_pending_change_for_project(project_id)
      results = p4.run_changes('-l', '-u', user, '-s', 'pending')

      change = results.find { |r| r['desc'].include?(project_id) }

      change['change'] if change
    end

    # If the user doesn't have a current pending change for the project,
    # create one, and return that.
    def create_pending_change(project_id)
      change = find_pending_change_for_project(project_id)

      return change if change

      change_spec = p4.fetch_change
      change_spec._description = "_hws_#{user}_#{project_id}"

      save_results = p4.save_change(change_spec)

      change = save_results.first.gsub(/Change (\d+) created./, '\1')

      # If we don't reset the client of this pending change, we won't be able
      # to cleanup the temporary client.
      change_spec = p4.fetch_change(change)
      change_spec._client = 'INVALID'
      p4.save_change(change_spec)

      change
    end

    def encode_name(name)
      HWSStrings.component_encode(name)
    end

    def unencode_name(name)
      HWSStrings.component_decode(name)
    end

    def server_uri_for_id(id)
      "p4://#{userinfo}#{server}#{safe_hve_projects_path}/#{id}"
    end

    def depot_path_for_name(name)
      "#{hve_projects_path}/#{name}"
    end

    def p4
      env['p4'] || fail('p4 required to use HVEProjects')
    end

    def hve_projects_path
      env['hws_settings'].HVE_PROJECTS_PATH || fail('HVE_PROJECTS_PATH not set')
    end

    def safe_hve_projects_path
      hve_projects_path.gsub('//', '/')
    end

    def user
      env['AUTH_CREDENTIALS'].first
    end

    # For HVE Projects, it may be interesting to people to see various
    # connection settings for each server URL.
    def userinfo
      data = {}
      if env['hws_settings'].P4CHARSET
        data['P4CHARSET'] = env['hws_settings'].P4CHARSET
      end
      if data.keys.empty?
        ''
      else
        encoded_data = data.map {|k,v| "#{k}=#{v}"}.join(';')
        "#{encoded_data}@"
      end
    end

    def server
      return p4port if p4port.include?(':')

      host = p4host ? p4host : 'localhost'
      port = p4port

      "#{host}:#{port}"
    end

    def p4port
      env['hws_settings'].P4PORT || fail('P4PORT setting not available')
    end

    def p4host
      env['hws_settings'].P4HOST
    end
  end
end
# Change User Description Committed
#1 15741 ptomiak Branch HWS for my use.
//guest/perforce_software/helix-web-services/main/source/helix_web_services/lib/helix_versioning_engine/hve_projects_service.rb
#1 15622 tjuricek Move source code to 'source/' subdirectory of branch.

build/ will remain where it is.
//guest/perforce_software/helix-web-services/main/helix_web_services/lib/helix_versioning_engine/hve_projects_service.rb
#5 15542 tjuricek Add spaces to our basic 'component encode' mechanism, and use it for HVE project IDs.

In general, this will make the HVE IDs a bit more readable.
#4 15497 tjuricek Add support to fetch the latest change of a project.
#3 15496 tjuricek Revise GET /helix_sync/v1/changes/[project] to /helix_sync/v1/changes/[project]/pending

The base method is really intended for the latest changelist number. Meh.
#2 15487 tjuricek Add basic ability to create pending changelists for helix sync projects.
#1 15479 tjuricek Added a basic "HVE project" implementation for creating clients.
//guest/perforce_software/helix-web-services/main/helix_web_services/lib/projects/hve_projects.rb
#1 15437 tjuricek Basic "HVE Project" implementation.

This will allow directories in a HVE instance to host 'projects' for use by helix sync.
There are no methods defined for creating the projects, however.

This does not include any specialization or testing in the Qt API yet.
I may investigate creating a "higher level" interface for Qt client apps.