require 'base64' require 'helix_versioning_engine/change_service' require 'helix_versioning_engine/util' require 'sinatra/base' require 'json' module HelixVersioningEngine # Add 'file' methods # # 'files' are actually a combination of path metadata. There are three # major kinds of file resources: depots, dirs, and files. The true file # resources can be a combination of details, along with the file content. class App < Sinatra::Base # Special depots only variant to match no path get '/p4/:api/files' do require_p4 p4 = env['p4'] path = params['path'] results = nil if path path = "//#{path}" unless path =~ %r{^//} results = p4.run_files(path) else results = p4.run_depots end results.to_json end # General browsing variation. # # Since we want this to be able to fetch file content in the case you # specify a file, versus a directory listing, we actually execute the # 'p4 files' command on the file. If we get a single result back, we then # add the base64'd content. If the file does not exist, we treat it like # a directory request. Thus, you'll never really get a 404, just an empty # array. get '/p4/:api/files/*' do require_p4 p4 = env['p4'] dirs = params[:splat].select { |x| !x.empty? } Util.assert_no_special_paths(dirs) results = nil if dirs.empty? results = p4.run_depots else file_selector = '//' + dirs.join('/') files_results = nil p4.at_exception_level(P4::RAISE_NONE) do files_results = p4.run_files(file_selector) end files_results = [] unless files_results if files_results.length == 1 && files_results.first.key?('depotFile') && files_results.first['depotFile'] == file_selector results = files_results[0] print_results = p4.run_print(file_selector) # The first result is a Hash "header" # Then you receive byte buffers until you receive an empty string content = ''.b print_results.drop(1).each do |result| if result content << result end end results['Content'] = Base64.encode64(content) else # Treat request like a directory list selector = '//' + dirs.join('/') + '/*' files_results = p4.run_files('-e', selector) dirs_results = p4.run_dirs(selector) results = files_results + dirs_results end end results.to_json end # File upload mechanism # # This allows for multi-file patching based on particular directory level. # Because sinatra doesn't really support JSON array bodies, this must be # specified via a 'Files' parameter. If that parameter exists, we consider # this to be a directory upload. # # If this is a directory upload, we expect the following parameters on each # array object: # # - 'Content' - The base64 content # - 'DepotFile' - the *relative* path from the main splat # # Otherwise, we mostly just care about the 'Content' # fields for single file uploads. # # In both cases, a 'Description' field can be used to indicate a release # message. patch '/p4/:api/files/*' do require_p4_with_temp_client p4 = env['p4'] client_root = env['p4_root'] client_name = p4.client path_parts = params[:splat].select { |x| !x.empty? } description = params['Description'] || 'Uploaded files' is_dir = params.key?('Files') Util.assert_no_special_paths(path_parts) files = nil if is_dir dir_root = "//#{path_parts.join('/')}" files = params['Files'].map do |f| { 'DepotFile' => "#{dir_root}/#{f['DepotFile']}", 'Content' => f['Content'] } end else files = [ { 'DepotFile' => "//#{path_parts.join('/')}", 'Content' => params[:Content] } ] end files.each { |f| f['Action'] = 'upload' } depot_paths = files.map { |f| f['DepotFile'] } change_service = ChangeService.new(p4: p4, client_root: client_root, client_name: client_name) files = files.map{ |f| ChangeService::File.from_json(f) } change_service.submit(files: files, description: description) '' end # Delete a single file. delete '/p4/:api/files/*' do require_p4_with_temp_client p4 = env['p4'] description = params['Description'] || 'Deleting file' path_parts = params[:splat].select { |x| !x.empty? } Util.assert_no_special_paths(path_parts) file_path = "//#{path_parts.join('/')}" change_id = Util.init_changelist(p4, description) begin p4.run_sync(file_path) p4.run_delete('-c', change_id, file_path) p4.run_submit('-c', change_id) rescue StandardError => ex p4.at_exception_level(P4::RAISE_NONE) do p4.run_change('-d', '-f', change_id) if Util.error?(p4) puts "possible issues deleting change #{change_id}: " \ "#{p4.messages}" end end raise ex end '' end end end