obf 0.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.
@@ -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