p4_web_api 2014.2.0.pre2 → 2014.2.0.pre4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,76 @@
1
+ module P4WebAPI
2
+ # Stream manipulation
3
+ #
4
+ # Streams are a little different from other spec types, in that the singluar
5
+ # 'stream' is identified by a depot-style path, as opposed to kind of a
6
+ # name. (And since our paths start with two slashes, we ignore those).
7
+ class App < Sinatra::Base
8
+ get '/v1/streams' do
9
+ streams = nil
10
+
11
+ open_p4 do |p4|
12
+ streams = p4.run_streams
13
+ end
14
+
15
+ normalize_streams(streams) if settings.normalize_output
16
+
17
+ streams.to_json
18
+ end
19
+
20
+ post '/v1/streams' do
21
+ open_p4 do |p4|
22
+ p4.save_stream(filter_params(params))
23
+ end
24
+
25
+ stream = params['Stream']
26
+
27
+ sub_path = stream[2..-1]
28
+
29
+ redirect "/v1/streams/#{sub_path}"
30
+ end
31
+
32
+ get '/v1/streams/*' do
33
+ sub_path = params[:splat].join('')
34
+
35
+ stream = "//#{sub_path}"
36
+
37
+ results = nil
38
+
39
+ open_p4 do |p4|
40
+ results = p4.run_stream('-o', stream)
41
+ end
42
+
43
+ normalize_streams(results) if settings.normalize_output
44
+
45
+ results[0].to_json
46
+ end
47
+
48
+ patch '/v1/streams/*' do
49
+ sub_path = params[:splat].join('')
50
+
51
+ stream = "//#{sub_path}"
52
+
53
+ open_p4 do |p4|
54
+ spec = p4.run_stream('-o', stream)[0]
55
+
56
+ spec = spec.merge(filter_params(params))
57
+
58
+ p4.save_stream(spec, '-f')
59
+ end
60
+
61
+ ''
62
+ end
63
+
64
+ delete '/v1/streams/*' do
65
+ sub_path = params[:splat].join('')
66
+
67
+ stream = "//#{sub_path}"
68
+
69
+ open_p4 do |p4|
70
+ p4.run_stream('-d', stream)
71
+ end
72
+
73
+ ''
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,31 @@
1
+ module P4WebAPI
2
+ # Add methods for triggers resources.
3
+ #
4
+ # Like protections, there is no singular 'trigger' resource in the Perforce
5
+ # system, just the list of all of them.
6
+ class App < Sinatra::Base
7
+ get '/v1/triggers' do
8
+ triggers = nil
9
+ open_p4 do |p4|
10
+ triggers = p4.run_triggers('-o').first
11
+ end
12
+
13
+ normalize_triggers(results) if settings.normalize_output
14
+
15
+ triggers.to_json
16
+ end
17
+
18
+ put '/v1/triggers' do
19
+ triggers = {
20
+ 'Triggers' => params['Triggers']
21
+ }
22
+
23
+ open_p4 do |p4|
24
+ p4.input = triggers
25
+ p4.run_triggers('-i')
26
+ end
27
+
28
+ ''
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,78 @@
1
+ module P4WebAPI
2
+ # Methods for user resources.
3
+ #
4
+ # Users are a little different from standard specs, since deleting users
5
+ # can require removing session data, etc.
6
+ class App < Sinatra::Base
7
+ get '/v1/users' do
8
+ results = nil
9
+
10
+ open_p4 do |p4|
11
+ results = p4.run_users
12
+ end
13
+
14
+ normalize_users(results) if settings.normalize_output
15
+
16
+ results.to_json
17
+ end
18
+
19
+ post '/v1/users' do
20
+ open_p4 do |p4|
21
+ p4.save_user(params, '-f')
22
+ end
23
+
24
+ user = params['User']
25
+
26
+ redirect "/v1/users/#{user}"
27
+ end
28
+
29
+ get '/v1/users/:user' do |user|
30
+ results = nil
31
+
32
+ open_p4 do |p4|
33
+ results = p4.run_user('-o', user)
34
+ end
35
+
36
+ normalize_users(results) if settings.normalize_output
37
+
38
+ if results.empty?
39
+ halt 404
40
+ else
41
+ results[0].to_json
42
+ end
43
+ end
44
+
45
+ patch '/v1/users/:user' do |user|
46
+ open_p4 do |p4|
47
+ results = p4.run_user('-o', user)
48
+
49
+ if results.empty?
50
+ halt 404
51
+ return
52
+ end
53
+
54
+ spec = results[0]
55
+ spec = spec.merge(filter_params(params))
56
+
57
+ spec['User'] = user unless spec.key?('User')
58
+
59
+ p4.save_user(spec, '-f')
60
+ end
61
+
62
+ # Avoid 'user jdoe saved' message from going out
63
+ ''
64
+ end
65
+
66
+ delete '/v1/users/:user' do |user|
67
+ open_p4 do |p4|
68
+ p4.run_user('-f', '-d', user)
69
+ end
70
+
71
+ # If you're deleting yourself (ragequitting via API) remove thy session
72
+ if user == env['AUTH_CREDENTIALS'].first
73
+ token = env['AUTH_CREDENTIALS'].last
74
+ P4WebAPI::Auth.delete_session(token, settings)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -134,7 +134,7 @@ module P4WebAPI
134
134
  # Takes an array with [login, password], and returns true if the password
135
135
  # is a legal token for that login, or looks like a p4 ticket.
136
136
  def self.credentials_valid?(credentials, settings)
137
- login = credentials.first
137
+ user = credentials.first
138
138
  password = credentials.last
139
139
  # Can't really determine much at this point, just return true. If we're
140
140
  # a token, it's a UUID and we'll learn more about that later.
@@ -142,7 +142,7 @@ module P4WebAPI
142
142
 
143
143
  user_info = read_token(password, settings)
144
144
  if user_info
145
- return user_info['login'] == login
145
+ return user_info['user'] == user
146
146
  else
147
147
  false
148
148
  end
@@ -258,7 +258,7 @@ module P4WebAPI
258
258
 
259
259
  def self.user_info(p4, results, ticket)
260
260
  {
261
- login: p4.user,
261
+ user: p4.user,
262
262
  email: results[0]['Email'],
263
263
  full_name: results[0]['FullName'],
264
264
  ticket: ticket
@@ -0,0 +1,149 @@
1
+ require 'base64'
2
+
3
+ module P4WebAPI
4
+ # This class assists in creating changelists based on an array of file
5
+ # changes, some of which may be file uploads.
6
+ #
7
+ # This is the implementation for file uploads and change creation.
8
+ class ChangeHelper
9
+ # The P4 connection we're using, that should already be logged in for a
10
+ # particular user.
11
+ attr_reader :p4
12
+
13
+ # The name of the temporary client
14
+ attr_reader :client_name
15
+
16
+ # The root directory of the temporary client
17
+ attr_reader :client_root
18
+
19
+ # Array of ChangeHelper::File instances.
20
+ attr_reader :files
21
+
22
+ # The change description
23
+ attr_reader :description
24
+
25
+ def initialize(p4: nil, client_name: nil, client_root: nil, files: [],
26
+ description: 'Updating files')
27
+ @p4 = p4
28
+ @client_name = client_name
29
+ @client_root = client_root
30
+ @files = files.map { |f| ChangeHelper::File.new(f) }
31
+ @description = description
32
+ end
33
+
34
+ def call
35
+ change_id = P4Util.init_changelist(p4, description)
36
+
37
+ begin
38
+ collect_file_results
39
+
40
+ files.each { |f| f.call(p4, change_id, client_root) }
41
+
42
+ p4.run_submit('-c', change_id)
43
+
44
+ rescue StandardError => ex
45
+ # Delete the changelist
46
+ p4.at_exception_level(P4::RAISE_NONE) do
47
+ p4.run_change('-d', '-f', change_id)
48
+ if P4Util.error?(p4)
49
+ puts "possible issues deleting change #{change_id}: #{p4.messages}"
50
+ end
51
+ end
52
+ raise ex
53
+ end
54
+ end
55
+
56
+ # Handles tracking 'file modification' operations on a single file for a
57
+ # change.
58
+ #
59
+ # One important aspect of initializing these files is assigning a 'files'
60
+ # result. The ChangeHelper pretty much grabs the output of 'p4 files' for
61
+ # all file changes, which lets us know if a file exists or not in the
62
+ # system, which changes downstream behavior during an upload process.
63
+ #
64
+ # Note that this will use our 'normalized' data style, where cases
65
+ class File
66
+ attr_accessor :depot_file
67
+
68
+ attr_accessor :action
69
+
70
+ # This should be a base64-encoded string
71
+ attr_accessor :content_base64
72
+
73
+ # For *some* actions, like a move, we need this indicated by the user.
74
+ attr_accessor :from_depot_file
75
+
76
+ # The output of p4 files on this depot_file path, if it exists.
77
+ attr_accessor :file_result
78
+
79
+ # Notice how the file *must* be specified using our 'external normalized'
80
+ # data style.
81
+ def initialize(obj = {})
82
+ @depot_file = obj['DepotFile'] if obj.key?('DepotFile')
83
+ @action = obj['Action'] if obj.key?('Action')
84
+ @from_depot_file = obj['FromDepotFile'] if obj.key?('FromDepotFile')
85
+ @content_base64 = obj['Content'] if obj.key?('Content')
86
+ end
87
+
88
+ def exists?
89
+ !file_result.nil?
90
+ end
91
+
92
+ #
93
+ # This really should only be called by ChangeHelper::call to do work
94
+ # once we have associated any file_result and from_file properties.
95
+ def call(p4, change_id, client_root)
96
+ case action
97
+ when 'upload'
98
+ upload_file(p4, change_id, client_root)
99
+ when 'branch'
100
+ integrate_file(p4, change_id)
101
+ when 'move'
102
+ move_file(p4, change_id)
103
+ end
104
+ end
105
+
106
+ def upload_file(p4, change_id, client_root)
107
+ if exists?
108
+ P4Util.mark_change('edit', p4, change_id, client_root, depot_file)
109
+ P4Util.save_content(client_root, depot_file, content)
110
+ else
111
+ P4Util.save_content(client_root, depot_file, content)
112
+ P4Util.mark_change('add', p4, change_id, client_root, depot_file)
113
+ end
114
+ end
115
+
116
+ def integrate_file(p4, change_id)
117
+ p4.run_integrate('-c', change_id, from_depot_file, depot_file)
118
+ end
119
+
120
+ def move_file(p4, change_id)
121
+ p4.run_move('-c', change_id, from_depot_file, depot_file)
122
+ end
123
+
124
+ def content
125
+ Base64.decode64(content_base64)
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ # Runs p4 files on all our depot paths, then updates each file model
132
+ # that exists.
133
+ def collect_file_results
134
+ file_results = p4.run_files(depot_files)
135
+ file_results.each do |file_result|
136
+ file = file_for_depot_file(file_result['depotFile'])
137
+ file.file_result = file_result if file
138
+ end
139
+ end
140
+
141
+ def depot_files
142
+ files.map(&:depot_file)
143
+ end
144
+
145
+ def file_for_depot_file(depot_file)
146
+ files.find { |f| f.depot_file == depot_file }
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,84 @@
1
+ require 'p4_web_api/p4_util'
2
+
3
+ module P4WebAPI
4
+ # Defines Sinatra helper methods in the main application.
5
+ module Helpers
6
+ # A block helper that uses the authentication credentials to create the p4
7
+ # connection
8
+ def open_p4(&block)
9
+ options = {
10
+ user: env['AUTH_CREDENTIALS'].first,
11
+ password: P4Util.resolve_password(env, settings),
12
+ host: P4Util.resolve_host(env, settings),
13
+ port: P4Util.resolve_port(env, settings)
14
+ }
15
+
16
+ charset = P4Util.resolve_charset(env, settings)
17
+ options[:charset] = charset if charset
18
+
19
+ P4Util.open(options, &block)
20
+ end
21
+
22
+ # Block helper to open a p4 handle with a temporary client workspace.
23
+ # The client workspace will map the series of depot path expressions
24
+ # directly into the client workspace.
25
+ #
26
+ # The depot_paths are generally expected to be complete file path
27
+ # expressions, e.g., '//depot/dir1/dir2/file'. They'll be mapped directly
28
+ # to the temporary working area: '//client/depot/dir/1/dir2/file'
29
+ #
30
+ # The block handler here will be called with the arguments (p4, root),
31
+ # where root is the temporary client's root directory.
32
+ def open_p4_temp_client(depot_paths, &block)
33
+ open_p4 do |p4|
34
+ name = (0...8).map { (65 + rand(26)).chr }.join
35
+ dir = init_temp_workspace_dir(name)
36
+ init_temp_client(p4, name, dir, depot_paths)
37
+
38
+ ex = nil
39
+ begin
40
+ block.call(p4, dir)
41
+ rescue StandardError => e
42
+ ex = e
43
+ end
44
+
45
+ delete_temp_client(p4, name, dir)
46
+
47
+ fail ex unless ex.nil?
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def init_temp_workspace_dir(name)
54
+ dir = File.join(settings.workspace_folder, name)
55
+ unless Dir.exist?(dir)
56
+ FileUtils.mkpath(dir)
57
+ FileUtils.chmod(0700, dir)
58
+ end
59
+ dir
60
+ end
61
+
62
+ def init_temp_client(p4, name, dir, depot_paths)
63
+ spec = p4.fetch_client
64
+ spec._root = dir
65
+ spec._client = name
66
+ spec._description = 'p4_web_api temp client'
67
+
68
+ spec._view = depot_paths.map do |path|
69
+ stripped = path.gsub(/^[\/]*/, '')
70
+ "\"//#{stripped}\" \"//#{name}/#{stripped}\""
71
+ end
72
+
73
+ p4.save_client(spec)
74
+ p4.client = name
75
+
76
+ p4.run_sync('//...')
77
+ end
78
+
79
+ def delete_temp_client(p4, name, dir)
80
+ p4.run_client('-d', '-f', name)
81
+ FileUtils.rmtree(dir)
82
+ end
83
+ end
84
+ end