lti_skydrive 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +5 -13
  2. data/app/controllers/skydrive/application_controller.rb +1 -5
  3. data/app/controllers/skydrive/files_controller.rb +9 -2
  4. data/app/controllers/skydrive/launch_controller.rb +30 -48
  5. data/app/models/skydrive/token.rb +3 -3
  6. data/app/models/skydrive/user.rb +14 -5
  7. data/config/database.yml +12 -0
  8. data/lib/skydrive.rb +18 -0
  9. data/lib/skydrive/client.rb +72 -70
  10. data/lib/skydrive/version.rb +1 -1
  11. data/spec/controllers/launch_controller_spec.rb +7 -6
  12. data/spec/lib/skydrive/client_spec.rb +14 -12
  13. data/spec/models/token_spec.rb +11 -0
  14. data/spec/models/user_spec.rb +10 -0
  15. data/spec/test_app/db/development.sqlite3 +0 -0
  16. data/spec/test_app/log/development.log +745 -0
  17. data/spec/test_app/log/test.log +126706 -0
  18. data/spec/test_app/tmp/cache/assets/development/sprockets/v3.0/CHer0H-jP4A29vBWMTWAbA1ebW-NTy1QcHxQQNhUjWI.cache +2 -0
  19. data/spec/test_app/tmp/cache/assets/development/sprockets/v3.0/E8jfClrIpMLhvceBJKxIqzyxqKwk7cFpeUS2k4v5-Z0.cache +2 -0
  20. data/spec/test_app/tmp/cache/assets/development/sprockets/v3.0/EDprT6sqMPxTBQ_xn87BbQDdbUiYEucPRjgX9jnkXn8.cache +0 -0
  21. data/spec/test_app/tmp/cache/assets/development/sprockets/v3.0/QV2A3C33F_F-8Qz2hetGKWH-udN8YinxyJmVmsaxuKg.cache +0 -0
  22. data/spec/test_app/tmp/cache/assets/development/sprockets/v3.0/SirARubcy2-5Q39YtSNP1UPQgyCDoYkKbAuOMOytAlI.cache +1 -0
  23. data/spec/test_app/tmp/cache/assets/development/sprockets/v3.0/aHYSgLVw9ymnQ_eoPOs69jNR6lm_wxODLy3icbB8JpM.cache +1 -0
  24. data/spec/test_app/tmp/cache/assets/development/sprockets/v3.0/fK-zjgNvFxRGR9SDYC-VoQqTGHNqT4ZEPyOMO0e6N04.cache +1 -0
  25. data/spec/test_app/tmp/cache/assets/development/sprockets/v3.0/iYvi4cyaXwne9RBdlEgyx1LnU50jcbdOzZbxunCdTSc.cache +1 -0
  26. data/spec/test_app/tmp/cache/assets/development/sprockets/v3.0/k6fnbFbe_ypmXHMJQJ-HfxU7Icqx27ISRVuIco1aw_w.cache +0 -0
  27. data/spec/test_app/tmp/cache/assets/development/sprockets/v3.0/luCRRGPFfhw0R0zIXUZVSESWEZMrgCJm6PlsVIqklFY.cache +0 -0
  28. data/spec/test_app/tmp/cache/assets/development/sprockets/v3.0/qfZOvJDW0QsPGhtchQAFiXDhjwMfLju_imf-Bi0eP-w.cache +1 -0
  29. data/spec/test_app/tmp/cache/assets/development/sprockets/v3.0/sDPjdtKgptekMPx7YYF90oeuv60XBkK7gt_qF7vScas.cache +2 -0
  30. metadata +56 -22
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- OWFmMTU0ZmUzNjQzOTljZWY5MjY2ODlmY2U1YWFjMDcyZTdjYjZjMg==
5
- data.tar.gz: !binary |-
6
- ZGUzNDk1ODEyMDE0ZTdmZmE3MTU3N2U1YmFmNmFjMzY5ZTkyNjFhOQ==
2
+ SHA1:
3
+ metadata.gz: 0b86a8e829a9306ff1eb0bb4fd553470803fb2c1
4
+ data.tar.gz: 7edd66d2c89d2e532bdaf8f4d835e32a08b1017a
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- NWYxMmU3NzFhYWZiODYyY2NhZGNmZjVkMjhlNDUzNmQ5ZDM1MWEyZjJjNDE5
10
- ZGFhOTJlZjE3NDdkMTM0MmZjZTcwZjNiNjA3OGQ5YzVlMGQzYmRkMjU1YzI4
11
- MzhjMTMwMjQzYzAzMWFlMzU3ZDQ0NTY2YTU1NTEyNDI1YjAzYTA=
12
- data.tar.gz: !binary |-
13
- OTRiMmM4ZGNhMTljZDFiMTYzYjM2MWQ5YTIxM2I2NGQ0OWZhODY3OGUyNWNh
14
- ZTZhNThiNDgxNWI0YjU4ZWUwZWZmYmM3OWU4NWI4MjgxYjkxMjkxZDgxZDcx
15
- YTkzODJiYzI4Zjg0MTIwZmJjODQ1ZWI2MTY3YzkwNjFmNzViMzY=
6
+ metadata.gz: 256f3e439614ec9318abecb8f20bb4670849a640cb4007ff4a626e6f1ec9b3520e77f9b906c512ce6332635c647ad16eff69d44601c0ab267545676ec99b8323
7
+ data.tar.gz: c3a020f97b8d890d8aad9b2a7b9d18a0914493238401e8445a065ea2c9324920c654907b137d2d0b30560e29cf9a58c450240cdc4094baa56b94dc825662cda4
@@ -38,11 +38,7 @@ module Skydrive
38
38
  end
39
39
 
40
40
  def skydrive_client
41
- @skydrive_client ||=
42
- Client.new(SHAREPOINT.merge(
43
- personal_url: current_user.token.personal_url,
44
- token: current_user.token.access_token
45
- ))
41
+ @skydrive_client ||= current_user.skydrive_client
46
42
  end
47
43
  end
48
44
  end
@@ -1,4 +1,5 @@
1
1
  require 'open-uri'
2
+ require 'mimemagic'
2
3
 
3
4
  module Skydrive
4
5
  class FilesController < ApplicationController
@@ -44,8 +45,14 @@ module Skydrive
44
45
  if api_key
45
46
  user = api_key.user
46
47
  uri = "#{user.token.personal_url}_api/Web/GetFileByServerRelativeUrl('#{params[:file].gsub(/ /, '%20')}')/$value"
47
- puts "URI: #{uri}"
48
- send_data open(uri, { "Authorization" => "Bearer #{user.token.access_token}"}, &:read)
48
+
49
+ send_data_options = {
50
+ filename: ::File.basename(params[:file]),
51
+ type: MimeMagic::by_extension(params[:file].match(/\.[a-z]+$/))
52
+ }
53
+
54
+ data = open(uri, { "Authorization" => "Bearer #{user.token.access_token}"}, &:read)
55
+ send_data(data, send_data_options)
49
56
  else
50
57
  render status: 401
51
58
  end
@@ -4,58 +4,45 @@ require 'ims/lti'
4
4
  module Skydrive
5
5
 
6
6
  ERROR_NO_API_KEY = "Unable to get an access token, your state is invalid"
7
- ERROR_JSON_PARSE = "JSON::ParserError"
8
- ERROR_SKY_DRIVE_API = "Skydrive::APIErrorException"
9
-
10
-
11
7
 
12
8
  class LaunchController < ApplicationController
13
9
  include ActionController::Cookies
14
10
  before_filter :ensure_authenticated_user, only: :skydrive_authorized
15
11
 
16
12
  def tool_provider
17
- require 'oauth/request_proxy/rack_request'
13
+ @tool_provider ||= begin
14
+ require 'oauth/request_proxy/rack_request'
15
+ @account = Account.where(key: params['oauth_consumer_key']).first
18
16
 
19
- @account = Account.where(key: params['oauth_consumer_key']).first
17
+ if @account
18
+ key = @account.key
19
+ secret = @account.secret
20
+ end
20
21
 
21
- if @account
22
- key = @account.key
23
- secret = @account.secret
24
- end
22
+ tp = IMS::LTI::ToolProvider.new(key, secret, params)
25
23
 
26
- tp = IMS::LTI::ToolProvider.new(key, secret, params)
24
+ if !key
25
+ tp.lti_errorlog = "Invalid consumer key or secret"
26
+ elsif !secret
27
+ tp.lti_errorlog = "Consumer key wasn't recognized"
28
+ elsif !tp.valid_request?(request)
29
+ tp.lti_errorlog = "The OAuth signature was invalid"
30
+ elsif Time.now.utc.to_i - tp.request_oauth_timestamp.to_i > 120
31
+ tp.lti_errorlog = "Your request is too old"
32
+ end
27
33
 
28
- if !key
29
- tp.lti_errorlog = "Invalid consumer key or secret"
30
- elsif !secret
31
- tp.lti_errorlog = "Consumer key wasn't recognized"
32
- elsif !tp.valid_request?(request)
33
- tp.lti_errorlog = "The OAuth signature was invalid"
34
- elsif Time.now.utc.to_i - tp.request_oauth_timestamp.to_i > 120
35
- tp.lti_errorlog = "Your request is too old"
34
+ tp
36
35
  end
37
-
38
- #
39
- ## this isn't actually checking anything like it should, just want people
40
- ## implementing real tools to be aware they need to check the nonce
41
- #if was_nonce_used_in_last_x_minutes?(@tp.request_oauth_nonce, 60)
42
- # register_error "Why are you reusing the nonce?"
43
- # return false
44
- #end
45
-
46
- return tp
47
36
  end
48
37
 
49
38
  def basic_launch
50
-
51
39
  tp = tool_provider
52
40
  if tp.lti_errorlog
53
41
  render text: tp.lti_errorlog, status: 400, layout: "skydrive/error"
54
42
  return
55
43
  end
56
44
 
57
- user_id = tp.get_custom_param('masquerading_user_id') || tp.user_id
58
- is_masquerading = user_id == tp.get_custom_param('masquerading_user_id')
45
+ user_id = is_masquerading && tp.get_custom_param('masquerading_user_id') || tp.user_id
59
46
  name = is_masquerading ? 'masqueraded session' : tp.lis_person_name_full
60
47
  email = is_masquerading ? 'masqueraded session' : tp.lis_person_contact_email_primary
61
48
 
@@ -72,8 +59,6 @@ module Skydrive
72
59
  user.name = name
73
60
  user.save!
74
61
  end
75
-
76
- user.ensure_token
77
62
  user.cleanup_api_keys
78
63
 
79
64
  code = user.session_api_key(params).oauth_code
@@ -81,17 +66,7 @@ module Skydrive
81
66
  end
82
67
 
83
68
  def skydrive_authorized
84
- skydrive_token = current_user.token
85
- if skydrive_token && skydrive_token.requires_refresh?
86
- begin
87
- skydrive_client.refresh_token = skydrive_token.refresh_token
88
- skydrive_token.refresh!(skydrive_client)
89
- rescue Skydrive::APIResponseErrorException => error
90
- current_user.reset_token!
91
- end
92
- end
93
-
94
- if skydrive_token && skydrive_token.is_valid?
69
+ if current_user.valid_skydrive_token?
95
70
  render json: {}, status: 201
96
71
  else
97
72
  code = current_user.api_keys.active.skydrive_oauth.create.oauth_code
@@ -102,19 +77,19 @@ module Skydrive
102
77
  def microsoft_oauth
103
78
  begin
104
79
  api_key = ApiKey.trade_oauth_code_for_access_token(params['state'])
105
- raise RuntimeError, ERROR_NO_API_KEY unless api_key
80
+ raise OAuthStateException, ERROR_NO_API_KEY unless api_key
106
81
 
107
82
  @current_user = api_key.user
108
83
  skydrive_client.request_oauth_token(params['code'], microsoft_oauth_url)
109
84
  service = skydrive_client.get_my_files_service()
110
85
  current_user.token.resource = service["serviceResourceId"]
111
- current_user.token.refresh!(skydrive_client)
86
+ current_user.token.refresh!
112
87
 
113
88
  personal_url = skydrive_client.get_personal_url(service["serviceEndpointUri"]).gsub(/\/Documents$/,'/')
114
89
  current_user.token.update_attribute(:personal_url, personal_url)
115
90
 
116
91
  redirect_to "#{root_path}oauth/callback"
117
- rescue Exception => api_error
92
+ rescue APIResponseErrorException, JSON::ParserError, OAuthStateException => api_error
118
93
  launch_exception_handler api_error
119
94
  end
120
95
  end
@@ -164,5 +139,12 @@ module Skydrive
164
139
 
165
140
  render action: :launch_error, status: 400, layout: "skydrive/error"
166
141
  end
142
+
143
+ def is_masquerading
144
+ @is_masquerading ||= begin
145
+ m_user_id = tool_provider.get_custom_param('masquerading_user_id')
146
+ !m_user_id.blank? && m_user_id != '$Canvas.masqueradingUser.userId'
147
+ end
148
+ end
167
149
  end
168
150
  end
@@ -4,16 +4,16 @@ module Skydrive
4
4
  belongs_to :user
5
5
 
6
6
  def requires_refresh?
7
- !!self.not_before && self.not_before < Time.now
7
+ !!(self.not_before && !self.not_before.is_a?(Time) && self.not_before < Time.now)
8
8
  end
9
9
 
10
10
  def is_valid?
11
11
  !!self.access_token && self.expires_on && self.expires_on > Time.now
12
12
  end
13
13
 
14
- def refresh!(client)
14
+ def refresh!
15
15
  results = {}
16
- results = client.refresh_token(resource: resource)
16
+ results = self.user.skydrive_client.update_api_tokens(resource: resource, refresh_token: refresh_token, token: access_token)
17
17
  if results.key? 'access_token'
18
18
  attrs = ['token_type', 'expires_in', 'expires_on', 'not_before', 'resource', 'access_token', 'refresh_token']
19
19
  update_attributes(results.reject{|a| !attrs.include?(a)})
@@ -2,6 +2,9 @@ module Skydrive
2
2
  class User < ActiveRecord::Base
3
3
  include ActiveModel::ForbiddenAttributesProtection
4
4
 
5
+ after_initialize :ensure_token
6
+ after_initialize :cleanup_api_keys
7
+
5
8
  has_many :api_keys
6
9
  has_one :token
7
10
  belongs_to :account
@@ -26,6 +29,14 @@ module Skydrive
26
29
  end
27
30
 
28
31
  def ensure_token
32
+ if self.token
33
+ begin
34
+ self.token.refresh! if self.token.requires_refresh?
35
+ rescue Skydrive::APIResponseErrorException, JSON::ParserError => error
36
+ self.reset_token!
37
+ end
38
+ end
39
+
29
40
  self.token = self.create_token unless self.token
30
41
  end
31
42
 
@@ -34,12 +45,10 @@ module Skydrive
34
45
  ensure_token
35
46
  end
36
47
 
37
- # Convenience method
38
- def onedrive_client
39
- @onedrive_client ||=
48
+ def skydrive_client
49
+ @skydrive_client ||=
40
50
  Client.new(SHAREPOINT.merge(
41
- personal_url: self.token.personal_url,
42
- token: self.token.access_token
51
+ user_token: self.token
43
52
  ))
44
53
  end
45
54
  end
@@ -0,0 +1,12 @@
1
+ development: &default
2
+ adapter: postgresql
3
+ encoding: unicode
4
+ database: lti_skydrive
5
+ host: localhost
6
+ pool: 5
7
+ username: canvas
8
+
9
+
10
+ test:
11
+ <<: *default
12
+ database: lti_skydrive_test
data/lib/skydrive.rb CHANGED
@@ -1,6 +1,24 @@
1
1
  require "skydrive/engine"
2
2
 
3
3
  module Skydrive
4
+
5
+
6
+ class OAuthStateException < RuntimeError
7
+ end
8
+
9
+ class APIErrorException < RuntimeError
10
+ end
11
+
12
+ class APIResponseErrorException < RuntimeError
13
+ attr_reader :response, :code, :description
14
+ def initialize(response)
15
+ @response = response
16
+ @code = response['error']
17
+ @description = response['error_description']
18
+ super("#{@code}: #{@description}\n#{response}")
19
+ end
20
+ end
21
+
4
22
  class << self
5
23
  attr_accessor :logger
6
24
  end
@@ -7,28 +7,29 @@ require 'skydrive/raven_logger'
7
7
 
8
8
 
9
9
  module Skydrive
10
- class APIErrorException < RuntimeError
11
- end
12
-
13
- class APIResponseErrorException < RuntimeError
14
- attr_reader :response, :code, :description
15
- def initialize(response)
16
- @response = response
17
- @code = response['error']
18
- @description = response['error_description']
19
- super("#{@code}: #{@description}\n#{response}")
20
- end
21
- end
22
-
23
10
  class Client
24
11
  include ActionView::Helpers::NumberHelper
25
12
 
26
- attr_accessor :client_id, :client_secret, :guid, :personal_url, :token, :refresh_token
13
+ attr_accessor :client_id, :client_secret, :guid, :user_token
27
14
 
28
15
  def initialize(options = {})
29
16
  options.each do |key, val|
30
17
  self.send("#{key}=", val) if self.respond_to?("#{key}=")
31
18
  end
19
+
20
+ RestClient.log = Skydrive.logger
21
+ end
22
+
23
+ def personal_url
24
+ user_token.personal_url
25
+ end
26
+
27
+ def token
28
+ user_token.access_token
29
+ end
30
+
31
+ def refresh_token
32
+ user_token.refresh_token
32
33
  end
33
34
 
34
35
  # URL used to authorize this app for a sharepoint tenant
@@ -59,8 +60,8 @@ module Skydrive
59
60
  RestClient.post endpoint, options do |response, request, result|
60
61
  log_restclient_response(response, request, result)
61
62
  results = format_results(parse_api_response(response))
62
- self.token = results['access_token']
63
- self.refresh_token = results['refresh_token']
63
+ self.user_token.access_token = results['access_token']
64
+ self.user_token.refresh_token = results['refresh_token']
64
65
  results
65
66
  end
66
67
  end
@@ -71,23 +72,25 @@ module Skydrive
71
72
  end
72
73
 
73
74
  def get_personal_url(service_endpoint_uri)
74
- self.personal_url = api_call("#{service_endpoint_uri}/files/root/weburl", {'Accept' => nil})['value']
75
+ self.user_token.personal_url = api_call("#{service_endpoint_uri}/files/root/weburl", {'Accept' => nil})['value']
75
76
  end
76
77
 
77
- def refresh_token(params)
78
+ def update_api_tokens(params)
78
79
  endpoint = 'https://login.windows.net/common/oauth2/token'
79
80
  options = {
80
81
  client_id: client_id,
81
82
  client_secret: client_secret,
82
83
  grant_type: 'refresh_token',
83
- refresh_token: @refresh_token
84
+ refresh_token: self.user_token.refresh_token
84
85
  }.merge(params)
85
86
 
87
+ Rails.logger.info("#{refresh_token} | #{params[:refresh_token]} | #{options[:refresh_token]}")
88
+
86
89
  RestClient.post endpoint, options do |response, request, result|
87
90
  log_restclient_response(response, request, result)
88
91
  results = format_results(parse_api_response(response))
89
- self.token = results['access_token']
90
- self.refresh_token = results['refresh_token']
92
+ self.user_token.access_token = results['access_token']
93
+ self.user_token.refresh_token = results['refresh_token']
91
94
  results
92
95
  end
93
96
  end
@@ -168,49 +171,16 @@ module Skydrive
168
171
  end
169
172
 
170
173
  def api_call(url, headers = {})
171
- begin
174
+ url.gsub!("https:/i", "https://i")
175
+ uri = URI.escape(url)
172
176
 
173
- url.gsub!("https:/i", "https://i")
174
- uri = URI.escape(url)
177
+ headers['Authorization'] = "Bearer #{self.user_token.access_token}" unless headers.has_key? 'Authorization'
178
+ headers['Accept'] = "application/json; odata=verbose" unless headers.has_key? 'Accept'
175
179
 
176
-
177
- headers['Authorization'] = "Bearer #{token}" unless headers.has_key? 'Authorization'
178
- headers['Accept'] = "application/json; odata=verbose" unless headers.has_key? 'Accept'
179
-
180
- c = Curl::Easy.new(uri) do |http|
181
- headers.each {|k,v| http.headers[k] = v if v }
182
- end
183
-
184
- headers = []
185
- buffer = ""
186
- c.on_body { |data|
187
- buffer << data
188
- data.size
189
- }
190
- c.on_header { |data|
191
- headers << data
192
- data.size
193
- }
194
- c.perform
195
-
196
- result = parse_api_response(buffer)
197
- rescue Exception => error
198
- pid = generate_pid
199
- headerOutput = c.headers.map {|k,v| "#{k}: #{v}"}.join("\n[#{pid}] - ")
200
- backtrace_output = error.backtrace.join("\n[#{pid}] - ")
201
- buffer_output = buffer.split("\n").join("\n[#{pid}] - ")
202
-
203
- Skydrive.logger.error("[#{pid}] SKYDRIVE ERROR: #{error.class.to_s} ◊ #{error}")
204
- Skydrive.logger.info("[#{pid}] SKYDRIVE BACKTRACE: \n[#{pid}] - #{backtrace_output}")
205
- Skydrive.logger.info("[#{pid}] SKYDRIVE REQUEST: #{uri.to_s}")
206
- Skydrive.logger.info("[#{pid}] SKYDRIVE REQUEST HEADERS:\n[#{pid}] - #{headerOutput}")
207
- Skydrive.logger.info("[#{pid}] SKYDRIVE RESPONSE HEADERS:\n[#{pid}] - #{headers.join(' - ')}")
208
- Skydrive.logger.info("[#{pid}] SKYDRIVE RESPONSE BODY:\n[#{pid}] - #{buffer_output}");
209
- Skydrive.logger.info("[#{pid}] END --\n");
210
- RavenLogger.capture_exception(error)
211
- raise error
180
+ result = RestClient.get uri, headers do |response, request, result|
181
+ log_restclient_response(response, request, result)
182
+ parse_api_response(response)
212
183
  end
213
-
214
184
  result["d"] || result
215
185
  end
216
186
 
@@ -221,17 +191,49 @@ module Skydrive
221
191
  private
222
192
 
223
193
  def log_restclient_response(response, request, result)
224
- pid = generate_pid
225
- Skydrive.logger.info("[#{pid}] SKYDRIVE REQUEST: #{request.url}")
226
- Skydrive.logger.info("[#{pid}] SKYDRIVE REQUEST PAYLOAD: #{request.payload}")
227
- headerOutput = request.headers.values.join("\n - ")
228
- Skydrive.logger.info("[#{pid}] SKYDRIVE REQUEST HEADERS:\n - #{headerOutput}")
229
- Skydrive.logger.info("[#{pid}] SKYDRIVE RESPONSE CODE: #{result.code}")
230
- Skydrive.logger.info("[#{pid}] SKYDRIVE RESPONSE BODY:\n#{response}")
194
+ response = format_log_lines response.split("\n")
195
+ request_headers = format_key_value_log_lines request.headers.merge(request.processed_headers)
196
+ response_headers = format_key_value_log_lines result.each_header
197
+ payload = request.args[:payload] || (request.payload && request.payload.empty? && request.payload) || '--No Payload!!--'
198
+
199
+ Skydrive.logger.info(%Q|
200
+ ==========================================================================
201
+ ========= BEGIN SKYDRIVE RestClient Response log [#{current_pid}] ========
202
+ Method: #{request.method}
203
+ Endpoint: #{request.url}
204
+ Headers: #{request_headers}
205
+ Payload: #{payload}
206
+ Response Code: #{result.code}
207
+ Response Headers: #{response_headers}
208
+ Response Body: #{response}
209
+ Caller: #{format_log_lines caller}
210
+ ========= END SKYDRIVE RestClient Response log [#{current_pid}] =========
211
+ =========================================================================|)
212
+ end
213
+
214
+ def format_key_value_log_lines(lines)
215
+ format_log_lines lines.map {|k, v| "$#{k} => '#{v}'"}
216
+ end
217
+
218
+ def format_log_lines(lines)
219
+ space = "\n "
220
+ "#{space}#{lines.join(space)}"
221
+ end
222
+
223
+ def log_error(error)
224
+ RavenLogger.capture_exception(error)
225
+ Skydrive.logger.error(%Q|
226
+ ==========================================================================
227
+ ========= BEGIN SKYDRIVE ERROR [#{current_pid}] ==========================
228
+ Error: #{error}
229
+ Class: #{error.class.to_s}
230
+ Caller: #{format_log_lines caller}
231
+ ========= END SKYDRIVE ERROR [#{current_pid}] ============================
232
+ ==========================================================================|)
231
233
  end
232
234
 
233
- def generate_pid
234
- (0...8).map { (65 + rand(26)).chr }.join
235
+ def current_pid
236
+ @pid ||= (0...8).map { (65 + rand(26)).chr }.join
235
237
  end
236
238
 
237
239
  def parse_api_response(body)