obf 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 65fdbd5291f1daf48d1d5699e7d9509b0ca9fdd9
4
+ data.tar.gz: 7daf99c0f9f09572a401c9482f154a4609414341
5
+ SHA512:
6
+ metadata.gz: 141e74853490736882aa1f4105cac797359cfd5f48cdca07618f3c760fed9e07cb015521f467a4c8fe9d44bfce08e69c9065e378e5332d467ff83ecf7edbbc3d
7
+ data.tar.gz: 0bc263d9575d1d972a9231c109060a6579e6879ac520798fd3a0756b2d6c8448995c44d9779edd883dcbb6bff420fdb7e4cbd6add064d232a3533564d42a2ed2
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2014 CoughDrop
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to use,
6
+ copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
7
+ Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
17
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,174 @@
1
+ # Canvas API
2
+
3
+ This ruby library is to make it easier to use the
4
+ [Canvas API](http://api.instructure.com).
5
+
6
+ ## Installation
7
+ This is packaged as the `canvas-api` rubygem, so you can just add the dependency to
8
+ your Gemfile or install the gem on your system:
9
+
10
+ gem install canvas-api
11
+
12
+ To require the library in your project:
13
+
14
+ require 'canvas-api'
15
+
16
+ ## Usage
17
+
18
+ ### OAuth Dance
19
+
20
+ Before you can make API calls you need an access token on behalf of the current user.
21
+ In order to get an access token you'll need to do the OAuth dance (and for that you'll
22
+ need a client_id and secret. Talk to the Canvas admin about getting these values):
23
+
24
+ ```ruby
25
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :client_id => 123, :secret => "abcdef")
26
+ url = canvas.oauth_url("https://my.site/oauth_success")
27
+ # => "https://canvas.example.com/login/oauth2/auth?client_id=123&response_type=code&redirect_uri=http%3A%2F%2Fmy.site%2Foauth_success
28
+ redirect to(url)
29
+ ```
30
+
31
+ And then when the browser redirects to oauth_success:
32
+
33
+ ```ruby
34
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :client_id => 123, :secret => "abcdef")
35
+ code = params['code']
36
+ canvas.retrieve_access_token(code, 'https://my.site/oauth_success') # this callback_url must match the one provided in the first step
37
+ # => {access_token: "qwert"}
38
+ ```
39
+ ### General API Calls
40
+
41
+ Once you've got an access token for a user you should save it (securely!) for future use. To use the API call:
42
+
43
+ ```ruby
44
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
45
+ canvas.get("/api/v1/users/self/profile")
46
+ # => {id: 90210, name: "Annie Wilson", ... }
47
+ ```
48
+
49
+ For POST and PUT requests the second parameter is the form parameters to append, either as a hash or
50
+ an array of arrays:
51
+
52
+ ```ruby
53
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
54
+ canvas.put("/api/v1/users/self", {'user[name]' => 'Dixon Wilson', 'user[short_name]' => 'Dixon'})
55
+ # => {id: 90210, name: "Dixon Wilson", ... }
56
+ canvas.put("/api/v1/users/self", {'user' => {'name' => 'Dixon Wilson', 'short_name' => 'Dixon'}}) # this is synonymous with the previous call
57
+ # => {id: 90210, name: "Dixon Wilson", ... }
58
+ canvas.put("/api/v1/users/self", [['user[name]', 'Dixon Wilson'],['user[short_name]', 'Dixon']]) # this is synonymous with the previous call
59
+ # => {id: 90210, name: "Dixon Wilson", ... }
60
+ ```
61
+
62
+ On GET requests you can either append query parameters to the actual path or as a hashed second argument:
63
+
64
+ ```ruby
65
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
66
+ canvas.get("/api/v1/users/self/enrollments?type[]=TeacherEnrollment&type[]=TaEnrollment")
67
+ # => [{id: 1234, course_id: 5678, ... }, {id: 2345, course_id: 6789, ...}]
68
+ canvas.get("/api/v1/users/self/enrollments", {'type' => ['TeacherEnrollment', 'TaEnrollment']}) # this is synonymous with the previous call
69
+ # => [{id: 1234, course_id: 5678, ... }, {id: 2345, course_id: 6789, ...}]
70
+ ```
71
+
72
+ ### Pagination
73
+
74
+ API endpoints that return lists are often paginated, meaning they will only return the first X results
75
+ (where X depends on the endpoint and, possibly, the per_page parameter you optionally set). To get more
76
+ results you'll need to make additional API calls:
77
+
78
+ ```ruby
79
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
80
+ list = canvas.get("/api/v1/calendar_events?all_events=true")
81
+ list.length
82
+ # => 50
83
+ list.more?
84
+ # => true (if there's another page of results)
85
+ list.next_page!
86
+ # => [...] (returns the next page of results)
87
+ list.length
88
+ # => 100 (also concatenates the results on to the previous list, if that's more convenient)
89
+ list.next_page!
90
+ # => [...]
91
+ list.length
92
+ # => 150
93
+ ```
94
+
95
+ ### Additional Utilities
96
+
97
+ There are also some helper methods that can make some of the other tricky parts of the Canvas API a little more approachable.
98
+
99
+ #### File Uploads
100
+
101
+ Uploading files ia typically a multi-step process. There are three different ways to upload
102
+ files.
103
+
104
+ Upload a file from the local file system:
105
+
106
+
107
+ ```ruby
108
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
109
+ canvas.upload_file_from_local("/api/v1/users/self/files", File.open("/path/to/file.jpg"), :content_type => "image/jpeg")
110
+ # => {id: 1, display_name: "file.jpg", ... }
111
+ ```
112
+
113
+ Upload a file synchronously from a remote URL:
114
+
115
+ ```ruby
116
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
117
+ canvas.upload_file_from_url("/api/v1/users/self/files", :name => "image.jpg", :size => 12345, :url => "http://www.example.com/image.jpg")
118
+ # => {id: 1, display_name: "image.jpg", ... }
119
+ ```
120
+
121
+ Upload a file asynchronouysly from a remote URL:
122
+
123
+ ```ruby
124
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
125
+ status_url = canvas.upload_file_from_url("/api/v1/users/self/files", :asynch => true, :name => "image.jpg", :size => 12345, :url => "http://www.example.com/image.jpg")
126
+ # => "/api/v1/file_status/url"
127
+ canvas.get(status_url)
128
+ # => {upload_status: "pending"}
129
+ canvas.get(status_url)
130
+ # => {upload_status: "ready", attachment: {id: 1, display_name: "image.jpg", ... } }
131
+ ```
132
+
133
+ ```ruby
134
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
135
+ status_url = canvas.upload_file_from_url("/api/v1/users/self/files", :asynch => true, :name => "image.jpg", :size => 12345, :url => "http://www.example.com/image.jpg")
136
+ # => "/api/v1/file_status/url"
137
+ canvas.get(status_url)
138
+ # => {upload_status: "errored", message: "Invalid response code, expected 200 got 404"}
139
+ ```
140
+
141
+ For any of these upload types you can optionally provide additional configuration parameters if
142
+ the upload endpoint is to an area of Canvas that supports folders (user files, course files, etc.)
143
+
144
+ ```ruby
145
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
146
+ #
147
+ # upload the file to a known folder with id 1234
148
+ canvas.upload_file_from_url("/api/v1/users/self/files", :parent_folder_id => 1234, :name => "image.jpg", :size => 12345, :url => "http://www.example.com/image.jpg")
149
+ # => {id: 1, display_name: "image.jpg", ... }
150
+ #
151
+ # upload the file to a folder with the path "/friends"
152
+ canvas.upload_file_from_url("/api/v1/users/self/files", :parent_folder_path => "/friends", :name => "image.jpg", :size => 12345, :url => "http://www.example.com/image.jpg")
153
+ # => {id: 1, display_name: "image.jpg", ... }
154
+ #
155
+ # rename this file instead of overwriting a file with the same name (overwrite is the default)
156
+ canvas.upload_file_from_url("/api/v1/users/self/files", :on_duplicate => "rename", :name => "image.jpg", :size => 12345, :url => "http://www.example.com/image.jpg")
157
+ # => {id: 1, display_name: "image.jpg", ... }
158
+ ```
159
+
160
+
161
+
162
+ #### SIS ID Encoding
163
+
164
+ In addition to regular IDs, Canvas supports [SIS IDs](https://canvas.instructure.com/doc/api/file.object_ids.html) defined
165
+ by other systems. Sometimes these IDs contain non-standard characters, which can cause problems when
166
+ trying to use them via the API. In those cases you can do the following:
167
+
168
+ ```ruby
169
+ sis_course_id = canvas.encode_id("sis_course_id", "r#-789")
170
+ # => "hex:sis_course_id:72232d373839"
171
+ canvas.get("/api/v1/courses/#{sis_course_id}/enrollments")
172
+ # => [...]
173
+ ```
174
+
@@ -0,0 +1,15 @@
1
+ require 'typhoeus'
2
+ require 'json'
3
+ require 'mime/types'
4
+ require 'base64'
5
+ require 'tempfile'
6
+ require 'prawn'
7
+
8
+ module OBF
9
+ require './lib/obf/external'
10
+ require './lib/obf/obf'
11
+ require './lib/obf/obz'
12
+ require './lib/obf/pdf'
13
+ require './lib/obf/png'
14
+ require './lib/obf/utils'
15
+ end
@@ -0,0 +1,406 @@
1
+ module OBF::External
2
+ def self.to_obf(hash, dest_path, path_hash=nil)
3
+ res = OBF::Utils.obf_shell
4
+ res['id'] = hash['id'] #board.global_id
5
+ res['name'] = hash['name'] #board.settings['name']
6
+ res['default_layout'] = hash['default_layout'] || 'landscape' #'landscape'
7
+ res['url'] = hash['url'] #"#{JsonApi::Json.current_host}/#{board.key}"
8
+ res['data_url'] = hash['data_url'] #"#{JsonApi::Json.current_host}/api/v1/boards/#{board.key}"
9
+ res['description_html'] = hash['description_html'] #board.settings['description']
10
+ res['license'] = OBF::Utils.parse_license(hash['license']) #board.settings['license'])
11
+ res['settings'] = {
12
+ 'private' => !!(hash['settings'] && hash['settings']['private']), #!board.public,
13
+ 'key' => hash['key'] #board.key.split(/\//, 2)[1]
14
+ }
15
+ grid = []
16
+
17
+ res['images'] = []
18
+ (hash['images'] || []).each do |original_image|
19
+ image = {
20
+ 'id' => original_image['id'],
21
+ 'width' => original_image['width'],
22
+ 'height' => original_image['height'],
23
+ 'license' => OBF::Utils.parse_license(original_image['license']),
24
+ 'url' => original_image['url'],
25
+ 'data_url' => original_image['data_url'],
26
+ 'content_type' => original_image['content_type']
27
+ }
28
+ if !path_hash
29
+ image['data'] = OBF::Utils.image_base64(image['url'])
30
+ else
31
+ if path_hash['images'] && path_hash['images'][image['id']]
32
+ image['path'] = path_hash['images'][image['id']]['path']
33
+ else
34
+ image_fetch = OBF::Utils.image_raw(image['url'])
35
+ if image_fetch
36
+ zip_path = "images/image_#{image['id']}#{image_fetch['extension']}"
37
+ path_hash['images'] ||= {}
38
+ path_hash['images'][image['id']] = {
39
+ 'path' => zip_path
40
+ }
41
+ path_hash['zip'].add(zip_path, image_fetch['data'])
42
+ image['path'] = zip_path
43
+ end
44
+ end
45
+ end
46
+ res['images'] << image
47
+ # image = board.button_images.detect{|i| i.global_id == original_button['image_id'] }
48
+ # image = {
49
+ # 'id' => image.global_id,
50
+ # 'width' => image.settings['width'],
51
+ # 'height' => image.settings['height'],
52
+ # 'license' => OBF::Utils.parse_license(image.settings['license']),
53
+ # 'url' => image.url,
54
+ # 'data_url' => "#{JsonApi::Json.current_host}/api/v1/images/#{image.global_id}",
55
+ # 'content_type' => image.settings['content_type']
56
+ # }
57
+ end
58
+
59
+ res['sounds'] = []
60
+ (hash['sounds'] || []).each do |original_sound|
61
+ sound = {
62
+ 'id' => original_sound['id'],
63
+ 'duration' => original_sound['duration'],
64
+ 'license' => OBF::Utils.parse_license(original_sound['license']),
65
+ 'url' => original_sound['url'],
66
+ 'data_url' => original_sound['data_url'],
67
+ 'content_type' => original_sound['content_type']
68
+ }
69
+ if !path_hash
70
+ sound['data'] = OBF::Utils.sound_base64(sound['url'])
71
+ else
72
+ if path_hash['sounds'] && path_hash['sounds'][sound['id']]
73
+ sound['path'] = path_hash['sounds'][sound['id']]['path']
74
+ else
75
+ sound_fetch = OBF::Utils.sound_raw(sound['url'])
76
+ if sound_fetch
77
+ zip_path = "sounds/sound_#{sound['id']}#{sound_fetch['extension']}"
78
+ path_hash['sounds'] ||= {}
79
+ path_hash['sounds'][sound['id']] = {
80
+ 'path' => zip_path
81
+ }
82
+ path_hash['zip'].add(zip_path, sound_fetch['data'])
83
+ sound['path'] = zip_path
84
+ end
85
+ end
86
+ sound['path'] = zip_path
87
+ end
88
+
89
+ res['sounds'] << sound
90
+
91
+ # sound = board.button_sounds.detect{|i| i.global_id == original_button['sound_id'] }
92
+ # sound = {
93
+ # 'id' => sound.global_id,
94
+ # 'duration' => sound.settings['duration'],
95
+ # 'license' => OBF::Utils.parse_license(sound.settings['license']),
96
+ # 'url' => sound.url,
97
+ # 'data_url' => "#{JsonApi::Json.current_host}/api/v1/sounds/#{sound.global_id}",
98
+ # 'content_type' => sound.settings['content_type']
99
+ # }
100
+ end
101
+
102
+ res['buttons'] = []
103
+ buttons = hash['buttons'] #board.settings['buttons']
104
+ button_count = buttons.length
105
+
106
+ buttons.each_with_index do |original_button, idx|
107
+ button = {
108
+ 'id' => original_button['id'],
109
+ 'label' => original_button['label'],
110
+ 'vocalization' => original_button['vocalization'],
111
+ 'left' => original_button['left'],
112
+ 'top' => original_button['top'],
113
+ 'width' => original_button['width'],
114
+ 'height' => original_button['height'],
115
+ 'border_color' => original_button['border_color'] || "#aaa",
116
+ 'background_color' => original_button['background_color'] || "#fff"
117
+ }
118
+ if original_button['load_board']
119
+ button['load_board'] = {
120
+ 'id' => original_button['load_board']['id'],
121
+ 'url' => original_button['load_board']['url'], #"#{JsonApi::Json.current_host}/#{original_button['load_board']['key']}",
122
+ 'data_url' => original_button['load_board']['data_url'] #"#{JsonApi::Json.current_host}/api/v1/boards/#{original_button['load_board']['key']}"
123
+ }
124
+ if path_hash && path_hash['included_boards'][original_button['load_board']['id']]
125
+ button['load_board']['path'] = "board_#{original_button['load_board']['id']}.obf"
126
+ end
127
+ end
128
+ if original_button['url']
129
+ button['url'] = original_button['url']
130
+ end
131
+ original_button.each do|key, val|
132
+ if key.match(/^ext_/)
133
+ button[key] = val
134
+ end
135
+ end
136
+ # if original_button['apps']
137
+ # button['ext_coughdrop_apps'] = original_button['apps']
138
+ # if original_button['apps']['web'] && original_button['apps']['web']['launch_url']
139
+ # button['url'] = original_button['apps']['web']['launch_url']
140
+ # end
141
+ # end
142
+ if original_button['image_id'] && hash['images']
143
+ image = res['images'].detect{|i| i['id'] == original_button['image_id']}
144
+ if image
145
+ button['image_id'] = image['id']
146
+ end
147
+ end
148
+ if original_button['sound_id']
149
+ sound = res['sounds'].detect{|s| s['id'] == original_button['sound_id']}
150
+ if sound
151
+ button['sound_id'] = sound['id']
152
+ end
153
+ end
154
+ res['buttons'] << button
155
+ OBF::Utils.update_current_progress(idx.to_f / button_count.to_f)
156
+ end
157
+ res['grid'] = OBF::Utils.parse_grid(hash['grid']) # TODO: more robust parsing here
158
+ if path_hash
159
+ zip_path = "board_#{res['id']}.obf"
160
+ path_hash['boards'] ||= {}
161
+ path_hash['boards'][res['id']] = {
162
+ 'path' => zip_path
163
+ }
164
+ path_hash['zip'].add(zip_path, JSON.pretty_generate(res))
165
+ else
166
+ File.open(dest_path, 'w') {|f| f.write(JSON.pretty_generate(res)) }
167
+ end
168
+ return dest_path
169
+ end
170
+
171
+ def self.from_obf(obf_json_or_path, opts)
172
+ obj = obf_json_or_path
173
+ if obj.is_a?(String)
174
+ obj = OBF::Utils.parse_obf(File.read(obf_json_or_path))
175
+ else
176
+ obj = OBF::Utils.parse_obf(obf_json_or_path)
177
+ end
178
+
179
+ ['images', 'sounds'].each do |type|
180
+ obj[type].each do |item|
181
+ item['data_or_url'] = item['data']
182
+ if !item['data_or_url'] && item['path'] && opts['zipper']
183
+ content_type = item['content_type']
184
+ data = opts['zipper'].read(item['path'])
185
+ str = "data:" + content_type
186
+ str += ";base64," + Base64.strict_encode64(data)
187
+ record = klass.create(:user => opts['user'])
188
+ item['data_or_url'] = str
189
+ end
190
+ item['data_or_url'] ||= item['url']
191
+ end
192
+ end
193
+
194
+ obj['license'] = OBF::Utils.parse_license(obj['license'])
195
+ obj
196
+
197
+ # raise "user required" unless opts['user']
198
+ # raise "missing id" unless obj['id']
199
+ #
200
+ # hashes = {}
201
+ # [['images_hash', ButtonImage], ['sounds_hash', ButtonSound]].each do |list, klass|
202
+ # obj[list].each do |id, item|
203
+ # record = nil
204
+ # unique_id = obj['id'] + "_" + item['id'].to_s
205
+ # if opts[list] && opts[list][unique_id]
206
+ # record = klass.find_by_global_id(opts[list][unique_id])
207
+ # elsif item['data']
208
+ # record = klass.create(:user => opts['user'])
209
+ # item['ref_url'] = item['data']
210
+ # elsif item['path'] && opts['zipper']
211
+ # content_type = item['content_type']
212
+ # data = opts['zipper'].read(item['path'])
213
+ # str = "data:" + content_type
214
+ # str += ";base64," + Base64.strict_encode64(data)
215
+ # record = klass.create(:user => opts['user'])
216
+ # item['ref_url'] = str
217
+ # elsif item['url']
218
+ # record = klass.create(:user => opts['user'])
219
+ # item['ref_url'] = item['url']
220
+ # end
221
+ # if record
222
+ # item.delete('data')
223
+ # item.delete('url')
224
+ # record.process(item)
225
+ # record.upload_to_remote(item['ref_url']) if item['ref_url']
226
+ # opts[list] ||= {}
227
+ # opts[list][unique_id] = record.global_id
228
+ # hashes[item['id']] = record.global_id
229
+ # end
230
+ # end
231
+ # end
232
+
233
+ # params = {}
234
+ # non_user_params = {'user' => opts['user']}
235
+ # params['name'] = obj['name']
236
+ # params['description'] = obj['description_html']
237
+ # params['image_url'] = obj['image_url']
238
+ # params['license'] = OBF::Utils.parse_license(obj['license'])
239
+ # params['buttons'] = obj['buttons'].map do |button|
240
+ # new_button = {
241
+ # 'id' => button['id'],
242
+ # 'label' => button['label'],
243
+ # 'vocalization' => button['vocalization'],
244
+ # 'left' => button['left'],
245
+ # 'top' => button['top'],
246
+ # 'width' => button['width'],
247
+ # 'height' => button['height'],
248
+ # 'border_color' => button['border_color'],
249
+ # 'background_color' => button['background_color']
250
+ # }
251
+ # if button['image_id']
252
+ # new_button['image_id'] = hashes[button['image_id']]
253
+ # end
254
+ # if button['sound_id']
255
+ # new_button['sound_id'] = hashes[button['sound_id']]
256
+ # end
257
+ # if button['load_board']
258
+ # if opts['boards'] && opts['boards'][button['load_board']['id']]
259
+ # new_button['load_board'] = opts['boards'][button['load_board']['id']]
260
+ # else
261
+ # link = Board.find_by_path(button['load_board']['key'] || button['load_board']['id'])
262
+ # if link
263
+ # new_button['load_board'] = {
264
+ # 'id' => link.global_id,
265
+ # 'key' => link.key
266
+ # }
267
+ # end
268
+ # end
269
+ # elsif button['url']
270
+ # if button['ext_coughdrop_apps']
271
+ # new_button['apps'] = button['ext_coughdrop_apps']
272
+ # else
273
+ # new_button['url'] = button['url']
274
+ # end
275
+ # end
276
+ # new_button
277
+ # end
278
+ # params['grid'] = obj['grid']
279
+ # params['public'] = !(obj['settings'] && obj['settings']['private'])
280
+ # non_user_params[:key] = (obj['settings'] && obj['settings']['key'])
281
+ # board = nil
282
+ # if opts['boards'] && opts['boards'][obj['id']]
283
+ # board = Board.find_by_path(opts['boards'][obj['id']]['id']) || Board.find_by_path(opts['boards'][obj['id']]['key'])
284
+ # board.process(params, non_user_params)
285
+ # else
286
+ # board = Board.process_new(params, non_user_params)
287
+ # opts['boards'] ||= {}
288
+ # opts['boards'][obj['id']] = {
289
+ # 'id' => board.global_id,
290
+ # 'key' => board.key
291
+ # }
292
+ # end
293
+ # board
294
+ #
295
+
296
+ end
297
+
298
+ def self.to_obz(boards, dest_path, opts)
299
+ paths = {}
300
+ root_board = boards[0]
301
+ OBF::Utils.build_zip(dest_path) do |zipper|
302
+ paths['zip'] = zipper
303
+ # board.track_downstream_boards!
304
+ paths['included_boards'] = {}
305
+ boards.each do |b|
306
+ paths['included_boards'][b['id']] = b
307
+ end
308
+ boards.each do |b|
309
+ b = paths['included_boards'][b['id']]
310
+ to_obf(b, nil, paths) if b
311
+ end
312
+ # board.settings['downstream_board_ids'].each do |id|
313
+ # b = Board.find_by_path(id)
314
+ # if b.allows?(opts['user'], 'view')
315
+ # paths['included_boards'][id] = b
316
+ # end
317
+ # end
318
+ # to_obf(board, nil, paths)
319
+ # board.settings['downstream_board_ids'].each do |id|
320
+ # b = paths['included_boards'][id]
321
+ # to_obf(b, nil, paths) if b
322
+ # end
323
+ manifest = {
324
+ 'root' => paths['boards'][root_board['id']]['path'],
325
+ 'paths' => {}
326
+ }
327
+ ['images', 'sounds', 'boards'].each do |type|
328
+ manifest['paths'][type] = {}
329
+ (paths[type] || {}).each do |id, opts|
330
+ manifest['paths'][type][id] = opts['path']
331
+ end
332
+ end
333
+
334
+ zipper.add('manifest.json', JSON.pretty_generate(manifest))
335
+ end
336
+ return dest_path
337
+ end
338
+
339
+ def self.from_obz(obz_path, opts)
340
+ result = []
341
+ OBF::Utils.load_zip(obz_path) do |zipper|
342
+ manifest = JSON.parse(zipper.read('manifest.json'))
343
+ root = manifest['root']
344
+ board = OBF::Utils.parse_obf(zipper.read(root))
345
+ board['path'] = root
346
+ unvisited_boards = [board]
347
+ visited_boards = []
348
+ obf_opts = {'zipper' => zipper, 'images' => {}, 'sounds' => {}, 'boards' => {}}
349
+ # obf_opts = {'user' => opts['user'], 'zipper' => 'zipper', 'images' => {}, 'sounds' => {}, 'boards' => {}}
350
+ while unvisited_boards.length > 0
351
+ board_object = unvisited_boards.shift
352
+ board_object['id'] ||= rand(9999).to_s + Time.now.to_i.to_s
353
+ visited_boards << board_object
354
+
355
+ board_object['buttons'].each do |button|
356
+ if button['load_board']
357
+ all_boards = visited_boards + unvisited_boards
358
+ if all_boards.none?{|b| b['id'] == button['load_board']['id'] || b['path'] == button['load_board']['path'] }
359
+ path = button['load_board']['path'] || manifest[button['load_board']['id']]
360
+ b = OBF::Utils.parse_obf(zipper.read(path))
361
+ b['path'] = path
362
+ unvisited_boards << b
363
+ end
364
+ end
365
+ end
366
+ end
367
+ visited_boards.each do |board_object|
368
+ result << from_obf(board_object, obf_opts)
369
+ end
370
+ end
371
+ return result
372
+ end
373
+
374
+ def self.to_pdf(board, dest_path, opts)
375
+ tmp_path = OBF::Utils.temp_path("stash")
376
+ if opts['packet']
377
+ OBF::Utils.as_progress_percent(0, 0.3) do
378
+ OBF::External.to_obz(board, tmp_path, opts)
379
+ end
380
+ OBF::Utils.as_progress_percent(0.3, 1.0) do
381
+ OBF::OBZ.to_pdf(tmp_path, dest_path)
382
+ end
383
+ else
384
+ OBF::Utils.as_progress_percent(0, 0.5) do
385
+ self.to_obf(board, tmp_path)
386
+ end
387
+ OBF::Utils.as_progress_percent(0.5, 1.0) do
388
+ OBF::OBF.to_pdf(tmp_path, dest_path)
389
+ end
390
+ end
391
+ File.unlink(tmp_path) if File.exist?(tmp_path)
392
+ dest_path
393
+ end
394
+
395
+ def self.to_png(board, dest_path)
396
+ tmp_path = OBF::Utils.temp_path("stash")
397
+ OBF::Utils.as_progress_percent(0, 0.5) do
398
+ self.to_pdf(board, tmp_path)
399
+ end
400
+ OBF::Utils.as_progress_percent(0.5, 1.0) do
401
+ OBF::PDF.to_png(tmp_path, dest_path)
402
+ end
403
+ File.unlink(tmp_path) if File.exist?(tmp_path)
404
+ dest_path
405
+ end
406
+ end