lti_skydrive 1.2.0 → 1.2.1

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.
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)