obf 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +18 -0
- data/README.md +174 -0
- data/lib/obf.rb +15 -0
- data/lib/obf/external.rb +406 -0
- data/lib/obf/obf.rb +17 -0
- data/lib/obf/obz.rb +13 -0
- data/lib/obf/pdf.rb +196 -0
- data/lib/obf/png.rb +22 -0
- data/lib/obf/utils.rb +265 -0
- data/lib/tinycolor_convert.js +969 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
+
|
data/lib/obf.rb
ADDED
@@ -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
|
data/lib/obf/external.rb
ADDED
@@ -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
|