p4_web_api 2014.2.0.pre2 → 2014.2.0.pre4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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