dropbox-sdk-forked_v2 1.0.0

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,113 @@
1
+ # ---------------------------------------------------------------------------------------
2
+ # A Rails 3 controller that:
3
+ # - Runs the through Dropbox's OAuth 2 flow, yielding a Dropbox API access token.
4
+ # - Makes a Dropbox API call to upload a file.
5
+ #
6
+ # To set up:
7
+ # 1. Create a Dropbox App key and secret to use the API. https://www.dropbox.com/developers
8
+ # 2. Add http://localhost:3000/dropbox/auth_finish as a Redirect URI for your Dropbox app.
9
+ # 3. Copy your App key and App secret into APP_KEY and APP_SECRET below.
10
+ #
11
+ # To run:
12
+ # 1. You need a Rails 3 project (to create one, run: rails new <folder-name>)
13
+ # 2. Copy dropbox_sdk.rb into <folder-name>/vendor
14
+ # 3. Copy this file into <folder-name>/app/controllers/
15
+ # 4. Add the following lines to <folder-name>/config/routes.rb
16
+ # get "dropbox/main"
17
+ # post "dropbox/upload"
18
+ # get "dropbox/auth_start"
19
+ # get "dropbox/auth_finish"
20
+ # 5. Run: rails server
21
+ # 6. Point your browser at: https://localhost:3000/dropbox/main
22
+
23
+ require 'dropbox_sdk'
24
+ require 'securerandom'
25
+
26
+ APP_KEY = ""
27
+ APP_SECRET = ""
28
+
29
+ class DropboxController < ApplicationController
30
+
31
+ def main
32
+ client = get_dropbox_client
33
+ unless client
34
+ redirect_to(:action => 'auth_start') and return
35
+ end
36
+
37
+ account_info = client.account_info
38
+
39
+ # Show a file upload page
40
+ render :inline =>
41
+ "#{account_info['email']} <br/><%= form_tag({:action => :upload}, :multipart => true) do %><%= file_field_tag 'file' %><%= submit_tag 'Upload' %><% end %>"
42
+ end
43
+
44
+ def upload
45
+ client = get_dropbox_client
46
+ unless client
47
+ redirect_to(:action => 'auth_start') and return
48
+ end
49
+
50
+ begin
51
+ # Upload the POST'd file to Dropbox, keeping the same name
52
+ resp = client.put_file(params[:file].original_filename, params[:file].read)
53
+ render :text => "Upload successful. File now at #{resp['path']}"
54
+ rescue DropboxAuthError => e
55
+ session.delete(:access_token) # An auth error means the access token is probably bad
56
+ logger.info "Dropbox auth error: #{e}"
57
+ render :text => "Dropbox auth error"
58
+ rescue DropboxError => e
59
+ logger.info "Dropbox API error: #{e}"
60
+ render :text => "Dropbox API error"
61
+ end
62
+ end
63
+
64
+ def get_dropbox_client
65
+ if session[:access_token]
66
+ begin
67
+ access_token = session[:access_token]
68
+ DropboxClient.new(access_token)
69
+ rescue
70
+ # Maybe something's wrong with the access token?
71
+ session.delete(:access_token)
72
+ raise
73
+ end
74
+ end
75
+ end
76
+
77
+ def get_web_auth()
78
+ redirect_uri = url_for(:action => 'auth_finish')
79
+ DropboxOAuth2Flow.new(APP_KEY, APP_SECRET, redirect_uri, session, :dropbox_auth_csrf_token)
80
+ end
81
+
82
+ def auth_start
83
+ authorize_url = get_web_auth().start()
84
+
85
+ # Send the user to the Dropbox website so they can authorize our app. After the user
86
+ # authorizes our app, Dropbox will redirect them here with a 'code' parameter.
87
+ redirect_to authorize_url
88
+ end
89
+
90
+ def auth_finish
91
+ begin
92
+ access_token, user_id, url_state = get_web_auth.finish(params)
93
+ session[:access_token] = access_token
94
+ redirect_to :action => 'main'
95
+ rescue DropboxOAuth2Flow::BadRequestError => e
96
+ render :text => "Error in OAuth 2 flow: Bad request: #{e}"
97
+ rescue DropboxOAuth2Flow::BadStateError => e
98
+ logger.info("Error in OAuth 2 flow: No CSRF token in session: #{e}")
99
+ redirect_to(:action => 'auth_start')
100
+ rescue DropboxOAuth2Flow::CsrfError => e
101
+ logger.info("Error in OAuth 2 flow: CSRF mismatch: #{e}")
102
+ render :text => "CSRF error"
103
+ rescue DropboxOAuth2Flow::NotApprovedError => e
104
+ render :text => "Not approved? Why not, bro?"
105
+ rescue DropboxOAuth2Flow::ProviderError => e
106
+ logger.info "Error in OAuth 2 flow: Error redirect from Dropbox: #{e}"
107
+ render :text => "Strange error."
108
+ rescue DropboxError => e
109
+ logger.info "Error getting OAuth 2 access token: #{e}"
110
+ render :text => "Error communicating with Dropbox servers."
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path('../../lib/dropbox_sdk', __FILE__)
2
+ require 'pp'
3
+
4
+ # You must use your Dropbox App key and secret to use the API.
5
+ # Find this at https://www.dropbox.com/developers
6
+ APP_KEY = ''
7
+ APP_SECRET = ''
8
+
9
+ def main
10
+ if APP_KEY == '' or APP_SECRET == ''
11
+ warn "ERROR: Set your APP_KEY and APP_SECRET at the top of search_cache.rb"
12
+ exit
13
+ end
14
+
15
+ prog_name = __FILE__
16
+ args = ARGV
17
+ if args.size != 2
18
+ warn "Usage: #{prog_name} <oauth1-access-token-key> <oauth1-access-token-secret>"
19
+ exit 1
20
+ end
21
+
22
+ access_token_key = args[0]
23
+ access_token_secret = args[1]
24
+
25
+ sess = DropboxSession.new(APP_KEY, APP_SECRET)
26
+ sess.set_access_token(access_token_key, access_token_secret)
27
+ c = DropboxClient.new(sess)
28
+
29
+ print "Creating OAuth 2 access token...\n"
30
+ oauth2_access_token = c.create_oauth2_access_token
31
+
32
+ print "Using OAuth 2 access token to get account info...\n"
33
+ c2 = DropboxClient.new(oauth2_access_token)
34
+ pp c2.account_info
35
+
36
+ print "Disabling OAuth 1 access token...\n"
37
+ c.disable_access_token
38
+ end
39
+
40
+ main()
@@ -0,0 +1,322 @@
1
+ # An example use of the /delta API call. Maintains a local cache of
2
+ # the App Folder's contents. Use the 'update' sub-command to update
3
+ # the local cache. Use the 'find' sub-command to search the local
4
+ # cache.
5
+ #
6
+ # Example usage:
7
+ #
8
+ # 1. Link to your Dropbox account
9
+ # > ruby search_cache.rb link
10
+ #
11
+ # 2. Go to Dropbox and make changes to the contents.
12
+ #
13
+ # 3. Update the local cache to match what's on Dropbox.
14
+ # > ruby search_cache.rb update
15
+ #
16
+ # 4. Search the local cache.
17
+ # > ruby search_cache.rb find 'txt'
18
+
19
+ # Repeat steps 2-4 any number of times.
20
+
21
+
22
+
23
+ require File.expand_path('../../lib/dropbox_sdk', __FILE__)
24
+ require 'json'
25
+
26
+ # You must use your Dropbox App key and secret to use the API.
27
+ # Find this at https://www.dropbox.com/developers
28
+ APP_KEY = ''
29
+ APP_SECRET = ''
30
+
31
+ STATE_FILE = 'search_cache.json'
32
+
33
+ def main()
34
+ if APP_KEY == '' or APP_SECRET == ''
35
+ warn "ERROR: Set your APP_KEY and APP_SECRET at the top of search_cache.rb"
36
+ exit
37
+ end
38
+ prog_name = __FILE__
39
+ args = ARGV
40
+ if args.size == 0
41
+ warn("Usage:\n")
42
+ warn(" #{prog_name} link Link to a user's account.")
43
+ warn(" #{prog_name} update Update cache to the latest on Dropbox.")
44
+ warn(" #{prog_name} update <num> Update cache, limit to <num> pages of /delta.")
45
+ warn(" #{prog_name} find <term> Search cache for <term>.")
46
+ warn(" #{prog_name} find Display entire cache contents")
47
+ warn(" #{prog_name} reset Delete the cache.")
48
+ exit
49
+ end
50
+
51
+ command = args[0]
52
+ if command == 'link'
53
+ command_link(args)
54
+ elsif command == 'update'
55
+ command_update(args)
56
+ elsif command == 'find'
57
+ command_find(args)
58
+ elsif command == 'reset'
59
+ command_reset(args)
60
+ else
61
+ warn "ERROR: Unknown command: #{command}"
62
+ warn "Run with no arguments for help."
63
+ exit(1)
64
+ end
65
+ end
66
+
67
+
68
+ def command_link(args)
69
+ if args.size != 1
70
+ warn "ERROR: \"link\" doesn't take any arguments"
71
+ exit
72
+ end
73
+
74
+ web_auth = DropboxOAuth2FlowNoRedirect.new(APP_KEY, APP_SECRET)
75
+ authorize_url = web_auth.start()
76
+ puts "1. Go to: #{authorize_url}"
77
+ puts "2. Click \"Allow\" (you might have to log in first)."
78
+ puts "3. Copy the authorization code."
79
+
80
+ print "Enter the authorization code here: "
81
+ STDOUT.flush
82
+ auth_code = STDIN.gets.strip
83
+
84
+ access_token, user_id = web_auth.finish(auth_code)
85
+ puts "Link successful."
86
+
87
+ save_state({
88
+ 'access_token' => access_token,
89
+ 'tree' => {}
90
+ })
91
+ end
92
+
93
+
94
+ def command_update(args)
95
+ if args.size == 1
96
+ page_limit = nil
97
+ elsif args.size == 2
98
+ page_limit = Integer(args[1])
99
+ else
100
+ warn "ERROR: \"update\" takes either zero or one argument."
101
+ exit
102
+ end
103
+
104
+ # Load state
105
+ state = load_state()
106
+ access_token = state['access_token']
107
+ cursor = state['cursor']
108
+ tree = state['tree']
109
+
110
+ # Connect to Dropbox
111
+ c = DropboxClient.new(access_token)
112
+
113
+ page = 0
114
+ changed = false
115
+ while (page_limit == nil) or (page < page_limit)
116
+ # Get /delta results from Dropbox
117
+ result = c.delta(cursor)
118
+ page += 1
119
+ if result['reset'] == true
120
+ puts 'reset'
121
+ changed = true
122
+ tree = {}
123
+ end
124
+ cursor = result['cursor']
125
+ # Apply the entries one by one to our cached tree.
126
+ for delta_entry in result['entries']
127
+ changed = true
128
+ apply_delta(tree, delta_entry)
129
+ end
130
+ cursor = result['cursor']
131
+ if not result['has_more']
132
+ break
133
+ end
134
+ end
135
+
136
+ # Save state
137
+ if changed
138
+ state['cursor'] = cursor
139
+ state['tree'] = tree
140
+ save_state(state)
141
+ else
142
+ puts "No updates."
143
+ end
144
+
145
+ end
146
+
147
+ # We track folder state as a tree of Node objects.
148
+ class Node
149
+ attr_accessor :path, :content
150
+ def initialize(path, content)
151
+ # The "original" page (i.e. not the lower-case path)
152
+ @path = path
153
+ # For files, content is a pair (size, modified)
154
+ # For folders, content is a hash of children Nodes, keyed by lower-case file names.
155
+ @content = content
156
+ end
157
+ def folder?()
158
+ @content.is_a? Hash
159
+ end
160
+ def to_json()
161
+ [@path, Node.to_json_content(@content)]
162
+ end
163
+ def self.from_json(jnode)
164
+ path, jcontent = jnode
165
+ Node.new(path, Node.from_json_content(jcontent))
166
+ end
167
+ def self.to_json_content(content)
168
+ if content.is_a? Hash
169
+ map_hash_values(content) { |child| child.to_json }
170
+ else
171
+ content
172
+ end
173
+ end
174
+ def self.from_json_content(jcontent)
175
+ if jcontent.is_a? Hash
176
+ map_hash_values(jcontent) { |jchild| Node.from_json jchild }
177
+ else
178
+ jcontent
179
+ end
180
+ end
181
+ end
182
+
183
+ # Run a mapping function over every value in a Hash, returning a new Hash.
184
+ def map_hash_values(h)
185
+ new = {}
186
+ h.each { |k,v| new[k] = yield v }
187
+ new
188
+ end
189
+
190
+
191
+ def apply_delta(root, e)
192
+ path, metadata = e
193
+ branch, leaf = split_path(path)
194
+
195
+ if metadata != nil
196
+ puts "+ #{path}"
197
+ # Traverse down the tree until we find the parent folder of the entry
198
+ # we want to add. Create any missing folders along the way.
199
+ children = root
200
+ branch.each do |part|
201
+ node = get_or_create_child(children, part)
202
+ # If there's no folder here, make an empty one.
203
+ if not node.folder?
204
+ node.content = {}
205
+ end
206
+ children = node.content
207
+ end
208
+
209
+ # Create the file/folder.
210
+ node = get_or_create_child(children, leaf)
211
+ node.path = metadata['path'] # Save the un-lower-cased path.
212
+ if metadata['is_dir']
213
+ # Only create a folder if there isn't one there already.
214
+ node.content = {} if not node.folder?
215
+ else
216
+ node.content = metadata['size'], metadata['modified']
217
+ end
218
+ else
219
+ puts "- #{path}"
220
+ # Traverse down the tree until we find the parent of the entry we
221
+ # want to delete.
222
+ children = root
223
+ missing_parent = false
224
+ branch.each do |part|
225
+ node = children[part]
226
+ # If one of the parent folders is missing, then we're done.
227
+ if node == nil or not node.folder?
228
+ missing_parent = true
229
+ break
230
+ end
231
+ children = node.content
232
+ end
233
+ # If we made it all the way, delete the file/folder.
234
+ if not missing_parent
235
+ children.delete(leaf)
236
+ end
237
+ end
238
+ end
239
+
240
+ def get_or_create_child(children, name)
241
+ child = children[name]
242
+ if child == nil
243
+ children[name] = child = Node.new(nil, nil)
244
+ end
245
+ child
246
+ end
247
+
248
+ def split_path(path)
249
+ bad, *parts = path.split '/'
250
+ [parts, parts.pop]
251
+ end
252
+
253
+
254
+ def command_find(args)
255
+ if args.size == 1
256
+ term = ''
257
+ elsif args.size == 2
258
+ term = args[1]
259
+ else
260
+ warn("ERROR: \"find\" takes either zero or one arguments.")
261
+ exit
262
+ end
263
+
264
+ state = load_state()
265
+ results = []
266
+ search_tree(results, state['tree'], term)
267
+ for r in results
268
+ puts("#{r}")
269
+ end
270
+ puts("[Matches: #{results.size}]")
271
+ end
272
+
273
+
274
+ def command_reset(args)
275
+ if args.size != 1
276
+ warn("ERROR: \"reset\" takes no arguments.")
277
+ exit
278
+ end
279
+
280
+ # Delete cursor, empty tree.
281
+ state = load_state()
282
+ if state.has_key?('cursor')
283
+ state.delete('cursor')
284
+ end
285
+ state['tree'] = {}
286
+ save_state(state)
287
+ end
288
+
289
+
290
+ # Recursively search 'tree' for files that contain the string in 'term'.
291
+ # Print out any matches.
292
+ def search_tree(results, tree, term)
293
+ tree.each do |name_lc, node|
294
+ path = node.path
295
+ if (path != nil) and path.include?(term)
296
+ if node.folder?
297
+ results.push("#{path}")
298
+ else
299
+ size, modified = node.content
300
+ results.push("#{path} (#{size}, #{modified})")
301
+ end
302
+ end
303
+ if node.folder?
304
+ search_tree(results, node.content, term)
305
+ end
306
+ end
307
+ end
308
+
309
+ def save_state(state)
310
+ state['tree'] = Node.to_json_content(state['tree'])
311
+ File.open(STATE_FILE,"w") do |f|
312
+ f.write(JSON.pretty_generate(state, :max_nesting => false))
313
+ end
314
+ end
315
+
316
+ def load_state()
317
+ state = JSON.parse(File.read(STATE_FILE), :max_nesting => false)
318
+ state['tree'] = Node.from_json_content(state['tree'])
319
+ state
320
+ end
321
+
322
+ main()
@@ -0,0 +1,193 @@
1
+ # -------------------------------------------------------------------
2
+ # An example webapp that lets you browse and upload files to Dropbox.
3
+ # Demonstrates:
4
+ # - The webapp OAuth process.
5
+ # - The metadata() and put_file() calls.
6
+ #
7
+ # To set up:
8
+ # 1. Create a Dropbox App key and secret to use the API. https://www.dropbox.com/developers
9
+ # 2. Add http://localhost:5000/dropbox-auth-finish as a Redirect URI for your Dropbox app.
10
+ # 3. Copy your App key and App secret into APP_KEY and APP_SECRET below.
11
+ #
12
+ # To run:
13
+ # 1. Install Sinatra $ gem install sinatra
14
+ # 2. Launch server $ ruby web_file_browser.rb
15
+ # 3. Browse to http://localhost:5000/
16
+ # -------------------------------------------------------------------
17
+
18
+ require 'rubygems'
19
+ require 'sinatra'
20
+ require 'pp'
21
+ require 'securerandom'
22
+ require File.expand_path('../../lib/dropbox_sdk', __FILE__)
23
+
24
+ # Get your app's key and secret from https://www.dropbox.com/developers/
25
+ APP_KEY = ''
26
+ APP_SECRET = ''
27
+
28
+ # -------------------------------------------------------------------
29
+ # OAuth stuff
30
+
31
+ def get_web_auth()
32
+ return DropboxOAuth2Flow.new(APP_KEY, APP_SECRET, url('/dropbox-auth-finish'),
33
+ session, :dropbox_auth_csrf_token)
34
+ end
35
+
36
+ get '/dropbox-auth-start' do
37
+ authorize_url = get_web_auth().start()
38
+
39
+ # Send the user to the Dropbox website so they can authorize our app. After the user
40
+ # authorizes our app, Dropbox will redirect them to our '/dropbox-auth-finish' endpoint.
41
+ redirect authorize_url
42
+ end
43
+
44
+ get '/dropbox-auth-finish' do
45
+ begin
46
+ access_token, user_id, url_state = get_web_auth.finish(params)
47
+ rescue DropboxOAuth2Flow::BadRequestError => e
48
+ return html_page "Error in OAuth 2 flow", "<p>Bad request to /dropbox-auth-finish: #{e}</p>"
49
+ rescue DropboxOAuth2Flow::BadStateError => e
50
+ return html_page "Error in OAuth 2 flow", "<p>Auth session expired: #{e}</p>"
51
+ rescue DropboxOAuth2Flow::CsrfError => e
52
+ logger.info("/dropbox-auth-finish: CSRF mismatch: #{e}")
53
+ return html_page "Error in OAuth 2 flow", "<p>CSRF mismatch</p>"
54
+ rescue DropboxOAuth2Flow::NotApprovedError => e
55
+ return html_page "Not Approved?", "<p>Why not, bro?</p>"
56
+ rescue DropboxOAuth2Flow::ProviderError => e
57
+ return html_page "Error in OAuth 2 flow", "Error redirect from Dropbox: #{e}"
58
+ rescue DropboxError => e
59
+ logger.info "Error getting OAuth 2 access token: #{e}"
60
+ return html_page "Error in OAuth 2 flow", "<p>Error getting access token</p>"
61
+ end
62
+
63
+ # In this simple example, we store the authorized DropboxSession in the session.
64
+ # A real webapp might store it somewhere more persistent.
65
+ session[:access_token] = access_token
66
+ redirect url('/')
67
+ end
68
+
69
+ get '/dropbox-unlink' do
70
+ session.delete(:access_token)
71
+ nil
72
+ end
73
+
74
+ # If we already have an authorized DropboxSession, returns a DropboxClient.
75
+ def get_dropbox_client
76
+ if session[:access_token]
77
+ return DropboxClient.new(session[:access_token])
78
+ end
79
+ end
80
+
81
+ # -------------------------------------------------------------------
82
+ # File/folder display stuff
83
+
84
+ get '/' do
85
+ # Get the DropboxClient object. Redirect to OAuth flow if necessary.
86
+ client = get_dropbox_client
87
+ unless client
88
+ redirect url("/dropbox-auth-start")
89
+ end
90
+
91
+ # Call DropboxClient.metadata
92
+ path = params[:path] || '/'
93
+ begin
94
+ entry = client.metadata(path)
95
+ rescue DropboxAuthError => e
96
+ session.delete(:access_token) # An auth error means the access token is probably bad
97
+ logger.info "Dropbox auth error: #{e}"
98
+ return html_page "Dropbox auth error"
99
+ rescue DropboxError => e
100
+ if e.http_response.code == '404'
101
+ return html_page "Path not found: #{h path}"
102
+ else
103
+ logger.info "Dropbox API error: #{e}"
104
+ return html_page "Dropbox API error"
105
+ end
106
+ end
107
+
108
+ if entry['is_dir']
109
+ render_folder(client, entry)
110
+ else
111
+ render_file(client, entry)
112
+ end
113
+ end
114
+
115
+ def render_folder(client, entry)
116
+ # Provide an upload form (so the user can add files to this folder)
117
+ out = "<form action='/upload' method='post' enctype='multipart/form-data'>"
118
+ out += "<label for='file'>Upload file:</label> <input name='file' type='file'/>"
119
+ out += "<input type='submit' value='Upload'/>"
120
+ out += "<input name='folder' type='hidden' value='#{h entry['path']}'/>"
121
+ out += "</form>" # TODO: Add a token to counter CSRF attacks.
122
+ # List of folder contents
123
+ entry['contents'].each do |child|
124
+ cp = child['path'] # child path
125
+ cn = File.basename(cp) # child name
126
+ if (child['is_dir']) then cn += '/' end
127
+ out += "<div><a style='text-decoration: none' href='/?path=#{h cp}'>#{h cn}</a></div>"
128
+ end
129
+
130
+ html_page "Folder: #{entry['path']}", out
131
+ end
132
+
133
+ def render_file(client, entry)
134
+ # Just dump out metadata hash
135
+ html_page "File: #{entry['path']}", "<pre>#{h entry.pretty_inspect}</pre>"
136
+ end
137
+
138
+ # -------------------------------------------------------------------
139
+ # File upload handler
140
+
141
+ post '/upload' do
142
+ # Check POST parameter.
143
+ file = params[:file]
144
+ unless file && (temp_file = file[:tempfile]) && (name = file[:filename])
145
+ return html_page "Upload error", "<p>No file selected.</p>"
146
+ end
147
+
148
+ # Get the DropboxClient object.
149
+ client = get_dropbox_client
150
+ unless client
151
+ return html_page "Upload error", "<p>Not linked with a Dropbox account.</p>"
152
+ end
153
+
154
+ # Call DropboxClient.put_file
155
+ begin
156
+ entry = client.put_file("#{params[:folder]}/#{name}", temp_file.read)
157
+ rescue DropboxAuthError => e
158
+ session.delete(:access_token) # An auth error means the access token is probably bad
159
+ logger.info "Dropbox auth error: #{e}"
160
+ return html_page "Dropbox auth error"
161
+ rescue DropboxError => e
162
+ logger.info "Dropbox API error: #{e}"
163
+ return html_page "Dropbox API error"
164
+ end
165
+
166
+ html_page "Upload complete", "<pre>#{h entry.pretty_inspect}</pre>"
167
+ end
168
+
169
+ # -------------------------------------------------------------------
170
+
171
+ def html_page(title, body='')
172
+ "<html>" +
173
+ "<head><title>#{h title}</title></head>" +
174
+ "<body><h1>#{h title}</h1>#{body}</body>" +
175
+ "</html>"
176
+ end
177
+
178
+ # Rack will issue a warning if no session secret key is set. A real web app would not have
179
+ # a hard-coded secret in the code but would load it from a config file.
180
+ use Rack::Session::Cookie, :secret => 'dummy_secret'
181
+
182
+ set :port, 5000
183
+ enable :sessions
184
+
185
+ helpers do
186
+ include Rack::Utils
187
+ alias_method :h, :escape_html
188
+ end
189
+
190
+ if APP_KEY == '' or APP_SECRET == ''
191
+ puts "You must set APP_KEY and APP_SECRET at the top of \"#{__FILE__}\"!"
192
+ exit 1
193
+ end