rpw 0.0.5 → 1.2.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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +109 -0
- data/.gitignore +1 -0
- data/Gemfile.lock +14 -16
- data/HISTORY.md +22 -0
- data/exe/rpw +1 -243
- data/lib/rpw.rb +4 -391
- data/lib/rpw/README.md +60 -0
- data/lib/rpw/cli.rb +268 -0
- data/lib/rpw/cli/bannerlord.rb +59 -0
- data/lib/rpw/cli/key.rb +17 -0
- data/lib/rpw/cli/quiz.rb +28 -0
- data/lib/rpw/cli/sub_command_base.rb +18 -0
- data/lib/rpw/client.rb +158 -0
- data/lib/rpw/client_data.rb +73 -0
- data/lib/rpw/gateway.rb +57 -0
- data/lib/rpw/version.rb +1 -1
- data/rpw.gemspec +2 -1
- metadata +27 -4
- data/lib/README.md +0 -11
data/lib/rpw/cli.rb
ADDED
@@ -0,0 +1,268 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "thor/hollaback"
|
3
|
+
require "rpw"
|
4
|
+
require "rpw/cli/bannerlord"
|
5
|
+
require "rpw/cli/sub_command_base"
|
6
|
+
require "rpw/cli/key"
|
7
|
+
require "cli/ui"
|
8
|
+
|
9
|
+
CLI::UI::StdoutRouter.enable
|
10
|
+
|
11
|
+
module RPW
|
12
|
+
class CLI < Thor
|
13
|
+
class_before :check_version
|
14
|
+
|
15
|
+
desc "key register [EMAIL_ADDRESS]", "Change email registered w/Speedshop"
|
16
|
+
subcommand "key", Key
|
17
|
+
|
18
|
+
def self.exit_on_failure?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "start", "Tutorial and onboarding"
|
23
|
+
def start
|
24
|
+
warn_if_already_started
|
25
|
+
|
26
|
+
print_banner
|
27
|
+
say "\u{1F48E} Welcome to the Rails Performance Workshop. \u{1F48E}"
|
28
|
+
say ""
|
29
|
+
say "This is rpw, the command line client for this workshop."
|
30
|
+
say ""
|
31
|
+
say "This client will download files from the internet into the current"
|
32
|
+
say "working directory, so it's best to run this client from a new directory"
|
33
|
+
say "that you'll use as your 'scratch space' for working on the Workshop."
|
34
|
+
say ""
|
35
|
+
|
36
|
+
ans = ::CLI::UI.confirm "Create files and folders in this directory? (no will quit)"
|
37
|
+
|
38
|
+
exit(1) unless ans
|
39
|
+
|
40
|
+
say ""
|
41
|
+
|
42
|
+
ans = ::CLI::UI::Prompt.ask("Where should we save your course progress?",
|
43
|
+
options: [
|
44
|
+
"here",
|
45
|
+
"my home directory (~/.rpw)"
|
46
|
+
])
|
47
|
+
|
48
|
+
client.directory_setup((ans == "my home directory (~/.rpw)"))
|
49
|
+
|
50
|
+
key = ::CLI::UI::Prompt.ask("Your Purchase Key: ")
|
51
|
+
|
52
|
+
unless client.setup(key)
|
53
|
+
say "That is not a valid key. Please try again."
|
54
|
+
exit(0)
|
55
|
+
end
|
56
|
+
|
57
|
+
say ""
|
58
|
+
say "Successfully authenticated with the RPW server and saved your key."
|
59
|
+
say ""
|
60
|
+
say "Setup complete!"
|
61
|
+
say ""
|
62
|
+
say "To learn how to use this command-line client, consult ./README.md,"
|
63
|
+
say "which we just created."
|
64
|
+
say ""
|
65
|
+
say "Once you've read that and you're ready to get going: $ rpw next"
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "next", "Proceed to the next lesson of the workshop"
|
69
|
+
option :"no-open", type: :boolean
|
70
|
+
def next
|
71
|
+
exit_with_no_key
|
72
|
+
content = client.next
|
73
|
+
|
74
|
+
if content.nil?
|
75
|
+
RPW::CLI.new.print_banner
|
76
|
+
say "Congratulations!"
|
77
|
+
say "You have completed the Rails Performance Workshop."
|
78
|
+
exit(0)
|
79
|
+
end
|
80
|
+
|
81
|
+
say "Proceeding to next lesson: #{content["title"]}"
|
82
|
+
client.download_and_extract(content)
|
83
|
+
client.complete(content["position"])
|
84
|
+
display_content(content, !options[:"no-open"])
|
85
|
+
end
|
86
|
+
|
87
|
+
desc "current", "Open the current lesson"
|
88
|
+
option :"no-open", type: :boolean
|
89
|
+
def current
|
90
|
+
exit_with_no_key
|
91
|
+
content = client.current
|
92
|
+
say "Opening: #{content["title"]}"
|
93
|
+
client.download_and_extract(content)
|
94
|
+
display_content(content, !options[:"no-open"])
|
95
|
+
end
|
96
|
+
|
97
|
+
desc "complete", "Mark the current lesson as complete"
|
98
|
+
def complete
|
99
|
+
say "Marked current lesson as complete"
|
100
|
+
client.complete(nil)
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "list", "Show all available workshop lessons"
|
104
|
+
def list
|
105
|
+
::CLI::UI::Frame.open("{{*}} {{bold:All Lessons}}", color: :green)
|
106
|
+
|
107
|
+
frame_open = false
|
108
|
+
client.list.each do |lesson|
|
109
|
+
if lesson["title"].start_with?("Section")
|
110
|
+
::CLI::UI::Frame.close(nil) if frame_open
|
111
|
+
::CLI::UI::Frame.open(lesson["title"])
|
112
|
+
frame_open = true
|
113
|
+
next
|
114
|
+
end
|
115
|
+
|
116
|
+
no_data = client.send(:client_data)["completed"].nil?
|
117
|
+
completed = client.send(:client_data)["completed"]&.include?(lesson["position"])
|
118
|
+
|
119
|
+
str = if no_data
|
120
|
+
""
|
121
|
+
elsif completed
|
122
|
+
"\u{2705} "
|
123
|
+
else
|
124
|
+
"\u{274C} "
|
125
|
+
end
|
126
|
+
|
127
|
+
case lesson["style"]
|
128
|
+
when "video"
|
129
|
+
puts str + ::CLI::UI.fmt("{{red:#{lesson["title"]}}}")
|
130
|
+
when "quiz"
|
131
|
+
# puts ::CLI::UI.fmt "{{green:#{" " + lesson["title"]}}}"
|
132
|
+
when "lab"
|
133
|
+
puts str + ::CLI::UI.fmt("{{yellow:#{" " + lesson["title"]}}}")
|
134
|
+
when "text"
|
135
|
+
puts str + ::CLI::UI.fmt("{{magenta:#{" " + lesson["title"]}}}")
|
136
|
+
else
|
137
|
+
puts str + ::CLI::UI.fmt("{{magenta:#{" " + lesson["title"]}}}")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
::CLI::UI::Frame.close(nil)
|
142
|
+
::CLI::UI::Frame.close(nil, color: :green)
|
143
|
+
end
|
144
|
+
|
145
|
+
desc "download", "Download all workshop contents"
|
146
|
+
def download
|
147
|
+
exit_with_no_key
|
148
|
+
total = client.list.size
|
149
|
+
client.list.each do |content|
|
150
|
+
current = client.list.index(content) + 1
|
151
|
+
puts "Downloading #{content["title"]} (#{current}/#{total})"
|
152
|
+
client.download_and_extract(content)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
desc "show", "Show any individal workshop lesson"
|
157
|
+
option :"no-open", type: :boolean
|
158
|
+
def show
|
159
|
+
exit_with_no_key
|
160
|
+
title = ::CLI::UI::Prompt.ask(
|
161
|
+
"Which lesson would you like to view?",
|
162
|
+
options: client.list.reject { |l| l["title"] == "Quiz" }.map { |l| " " * l["indent"] + l["title"] }
|
163
|
+
)
|
164
|
+
title.strip!
|
165
|
+
content_order = client.list.find { |l| l["title"] == title }["position"]
|
166
|
+
content = client.show(content_order)
|
167
|
+
client.download_and_extract(content)
|
168
|
+
display_content(content, !options[:"no-open"])
|
169
|
+
end
|
170
|
+
|
171
|
+
desc "set_progress", "Set current lesson to a particular lesson"
|
172
|
+
def set_progress
|
173
|
+
title = ::CLI::UI::Prompt.ask(
|
174
|
+
"Which lesson would you like to set your progress to? All prior lessons will be marked complete",
|
175
|
+
options: client.list.reject { |l| l["title"] == "Quiz" }.map { |l| " " * l["indent"] + l["title"] }
|
176
|
+
)
|
177
|
+
title.strip!
|
178
|
+
content_order = client.list.find { |l| l["title"] == title }["position"]
|
179
|
+
content = client.set_progress(content_order, all_prior: true)
|
180
|
+
say "Setting current progress to #{content.last["title"]}"
|
181
|
+
end
|
182
|
+
|
183
|
+
desc "reset", "Erase all progress and start over"
|
184
|
+
def reset
|
185
|
+
return unless ::CLI::UI.confirm("Are you sure you want to erase all of your progress?", default: false)
|
186
|
+
say "Resetting progress."
|
187
|
+
client.set_progress(nil)
|
188
|
+
end
|
189
|
+
|
190
|
+
no_commands do
|
191
|
+
def print_banner
|
192
|
+
RPW::Bannerlord.print_banner
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def exit_with_no_key
|
199
|
+
unless client.setup?
|
200
|
+
say "You have not yet set up the client. Run $ rpw start"
|
201
|
+
exit(1)
|
202
|
+
end
|
203
|
+
unless client.directories_ready?
|
204
|
+
say "You are not in your workshop scratch directory, or you have not yet"
|
205
|
+
say "set up the client. Change directory or run $ rpw start"
|
206
|
+
exit(1)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def client
|
211
|
+
@client ||= RPW::Client.new
|
212
|
+
end
|
213
|
+
|
214
|
+
def display_content(content, open_after)
|
215
|
+
openable = false
|
216
|
+
case content["style"]
|
217
|
+
when "video"
|
218
|
+
location = "video/#{content["s3_key"]}"
|
219
|
+
openable = true
|
220
|
+
when "quiz"
|
221
|
+
Quiz.start(["give_quiz", "quiz/" + content["s3_key"]])
|
222
|
+
when "lab"
|
223
|
+
location = "lab/#{content["s3_key"][0..-8]}"
|
224
|
+
openable = true
|
225
|
+
when "text"
|
226
|
+
location = "text/#{content["s3_key"]}"
|
227
|
+
openable = true
|
228
|
+
when "cgrp"
|
229
|
+
say "The Complete Guide to Rails Performance has been downloaded and extracted to the ./cgrp directory."
|
230
|
+
say "All source code for the CGRP is in the src directory, PDF and other compiled formats are in the release directory."
|
231
|
+
say "You can check it out now, or to continue: $ rpw next "
|
232
|
+
end
|
233
|
+
if location
|
234
|
+
if openable && !open_after
|
235
|
+
say "Download complete. Open with: $ #{open_command} #{location}"
|
236
|
+
elsif open_after && openable
|
237
|
+
exec "#{open_command} #{location}"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
require "rbconfig"
|
243
|
+
def open_command
|
244
|
+
host_os = RbConfig::CONFIG["host_os"]
|
245
|
+
case host_os
|
246
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
247
|
+
"start"
|
248
|
+
when /darwin|mac os/
|
249
|
+
"open"
|
250
|
+
else
|
251
|
+
"xdg-open"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def warn_if_already_started
|
256
|
+
return unless client.setup?
|
257
|
+
exit(0) unless ::CLI::UI.confirm "You have already started the workshop. Continuing "\
|
258
|
+
"this command will wipe all of your current progress. Continue?", default: false
|
259
|
+
end
|
260
|
+
|
261
|
+
def check_version
|
262
|
+
unless client.latest_version?
|
263
|
+
say "WARNING: You are running an old version of rpw."
|
264
|
+
say "WARNING: Please run `$ gem install rpw`"
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module RPW
|
2
|
+
class Bannerlord
|
3
|
+
class << self
|
4
|
+
def print_banner
|
5
|
+
puts r
|
6
|
+
if `tput cols 80`.to_i < 80
|
7
|
+
puts small_banner
|
8
|
+
else
|
9
|
+
puts banner
|
10
|
+
end
|
11
|
+
puts reset
|
12
|
+
end
|
13
|
+
|
14
|
+
def r
|
15
|
+
"\e[31m"
|
16
|
+
end
|
17
|
+
|
18
|
+
def reset
|
19
|
+
"\e[0m"
|
20
|
+
end
|
21
|
+
|
22
|
+
def small_banner
|
23
|
+
%(
|
24
|
+
_____ _ _____ _ _
|
25
|
+
|_ _| |_ ___ | __ |___|_| |___
|
26
|
+
| | | | -_| | -| .'| | |_ -|
|
27
|
+
|_| |_|_|___| |__|__|__,|_|_|___|
|
28
|
+
_____ ___
|
29
|
+
| _ |___ ___| _|___ ___ _____ ___ ___ ___ ___
|
30
|
+
| __| -_| _| _| . | _| | .'| | _| -_|
|
31
|
+
|__| |___|_| |_| |___|_| |_|_|_|__,|_|_|___|___|
|
32
|
+
_ _ _ _ _
|
33
|
+
| | | |___ ___| |_ ___| |_ ___ ___
|
34
|
+
| | | | . | _| '_|_ -| | . | . |
|
35
|
+
|_____|___|_| |_,_|___|_|_|___| _|
|
36
|
+
|_|
|
37
|
+
#{reset})
|
38
|
+
end
|
39
|
+
|
40
|
+
def banner
|
41
|
+
%(
|
42
|
+
_____ _ _____ _ _
|
43
|
+
+hmNMMMMMm/` -ymMMNh/ |_ _| |_ ___ | __ |___|_| |___
|
44
|
+
sMMMMMMMMMy +MMMMMMMMy | | | | -_| | -| .'| | |_ -|
|
45
|
+
yMMMMMMMMMMy` yMMMMMMMMN |_| |_|_|___| |__|__|__,|_|_|___|
|
46
|
+
`dMMMMMMMMMMm:-dMMMMMMm: _____ ___
|
47
|
+
`sNMMMMMMMMMMs.:+sso:` | _ |___ ___| _|___ ___ _____ ___ ___ ___ ___
|
48
|
+
:dMMMMMMMMMMm/ | __| -_| _| _| . | _| | .'| | _| -_|
|
49
|
+
:oss+:.sNMMMMMMMMMMy` |__| |___|_| |_| |___|_| |_|_|_|__,|_|_|___|___|
|
50
|
+
/mMMMMMMd-:mMMMMMMMMMMd. _ _ _ _ _
|
51
|
+
NMMMMMMMMy `hMMMMMMMMMMh | | | |___ ___| |_ ___| |_ ___ ___
|
52
|
+
yMMMMMMMM+ `dMMMMMMMMMy | | | | . | _| '_|_ -| | . | . |
|
53
|
+
/hNMMmy- `/mMMMMMNmy/ |_____|___|_| |_,_|___|_|_|___| _|
|
54
|
+
|_|
|
55
|
+
#{reset})
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/rpw/cli/key.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module RPW
|
2
|
+
class Key < SubCommandBase
|
3
|
+
desc "register [EMAIL_ADDRESS]", "Change email registered with Speedshop. One-time only."
|
4
|
+
def register(email)
|
5
|
+
unless client.setup?
|
6
|
+
say "You have not yet set up the client. Run $ rpw start"
|
7
|
+
exit(1)
|
8
|
+
end
|
9
|
+
if client.register_email(email)
|
10
|
+
say "Key registered with #{email}. You should receive a Slack invite soon."
|
11
|
+
else
|
12
|
+
say "Key has already been registered. If you believe this is in error,"\
|
13
|
+
" please email support@speedshop.co"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/rpw/cli/quiz.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "digest"
|
2
|
+
require "thor"
|
3
|
+
|
4
|
+
module RPW
|
5
|
+
class Quiz < Thor
|
6
|
+
desc "give_quiz FILENAME", ""
|
7
|
+
def give_quiz(filename)
|
8
|
+
@quiz_data = YAML.safe_load(File.read(filename))
|
9
|
+
@quiz_data["questions"].each { |q| question(q) }
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def question(data)
|
15
|
+
puts data["prompt"]
|
16
|
+
data["answer_choices"].each { |ac| puts ac }
|
17
|
+
provided_answer = ::CLI::UI::Prompt.ask("Your answer?", options: %w[A B C D])
|
18
|
+
answer_digest = Digest::MD5.hexdigest(data["prompt"] + provided_answer)
|
19
|
+
if answer_digest == data["answer_digest"]
|
20
|
+
say "Correct!"
|
21
|
+
else
|
22
|
+
say "Incorrect."
|
23
|
+
say "I encourage you to try reviewing the material to see what the correct answer is."
|
24
|
+
end
|
25
|
+
say ""
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RPW
|
2
|
+
class SubCommandBase < Thor
|
3
|
+
def self.banner(command, namespace = nil, subcommand = false)
|
4
|
+
"#{basename} #{subcommand_prefix} #{command.usage}"
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.subcommand_prefix
|
8
|
+
name.gsub(%r{.*::}, "").gsub(%r{^[A-Z]}) { |match| match[0].downcase }
|
9
|
+
.gsub(%r{[A-Z]}) { |match| "-#{match[0].downcase}" }
|
10
|
+
end
|
11
|
+
|
12
|
+
no_commands do
|
13
|
+
def client
|
14
|
+
@client ||= RPW::Client.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/rpw/client.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "rpw/cli/quiz"
|
3
|
+
|
4
|
+
module RPW
|
5
|
+
class Client
|
6
|
+
RPW_SERVER_DOMAIN = ENV["RPW_SERVER_DOMAIN"] || "https://rpw-licensor.speedshop.co"
|
7
|
+
attr_reader :gateway
|
8
|
+
|
9
|
+
def initialize(gateway = nil)
|
10
|
+
@gateway = gateway || Gateway.new(RPW_SERVER_DOMAIN, client_data["key"])
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup(key)
|
14
|
+
success = gateway.authenticate_key(key)
|
15
|
+
client_data["key"] = key if success
|
16
|
+
success
|
17
|
+
end
|
18
|
+
|
19
|
+
def register_email(email)
|
20
|
+
gateway.register_email(email)
|
21
|
+
end
|
22
|
+
|
23
|
+
def next
|
24
|
+
return list.first unless client_data["completed"]
|
25
|
+
list.sort_by { |c| c["position"] }.find { |c| c["position"] > current_position }
|
26
|
+
end
|
27
|
+
|
28
|
+
def current
|
29
|
+
return list.first unless client_data["completed"]
|
30
|
+
list.sort_by { |c| c["position"] }.find { |c| c["position"] == current_position }
|
31
|
+
end
|
32
|
+
|
33
|
+
def list
|
34
|
+
@list ||= begin
|
35
|
+
if client_data["content_cache_generated"] &&
|
36
|
+
client_data["content_cache_generated"] >= Time.now - 60 * 60
|
37
|
+
|
38
|
+
client_data["content_cache"]
|
39
|
+
else
|
40
|
+
begin
|
41
|
+
client_data["content_cache"] = gateway.list_content
|
42
|
+
client_data["content_cache_generated"] = Time.now
|
43
|
+
client_data["content_cache"]
|
44
|
+
rescue
|
45
|
+
client_data["content_cache"] || (raise Error.new("No internet connection"))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def show(content_pos)
|
52
|
+
list.find { |l| l["position"] == content_pos }
|
53
|
+
end
|
54
|
+
|
55
|
+
def directory_setup(home_dir_ok = true)
|
56
|
+
["video", "quiz", "lab", "text", "cgrp"].each do |path|
|
57
|
+
FileUtils.mkdir_p(path) unless File.directory?(path)
|
58
|
+
end
|
59
|
+
|
60
|
+
if home_dir_ok
|
61
|
+
ClientData.create_in_home!
|
62
|
+
else
|
63
|
+
ClientData.create_in_pwd!
|
64
|
+
end
|
65
|
+
|
66
|
+
unless File.exist?(".gitignore") && File.read(".gitignore").match(/rpw_info/)
|
67
|
+
File.open(".gitignore", "a") do |f|
|
68
|
+
f.puts "\n"
|
69
|
+
f.puts ".rpw_info\n"
|
70
|
+
f.puts "video\n"
|
71
|
+
f.puts "quiz\n"
|
72
|
+
f.puts "lab\n"
|
73
|
+
f.puts "text\n"
|
74
|
+
f.puts "cgrp\n"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
File.open("README.md", "w+") do |f|
|
79
|
+
f.puts File.read(File.join(File.dirname(__FILE__), "README.md"))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_progress(pos, all_prior: false)
|
84
|
+
client_data["completed"] = [] && return if pos.nil?
|
85
|
+
if all_prior
|
86
|
+
lessons = list.select { |l| l["position"] <= pos }
|
87
|
+
client_data["completed"] = lessons.map { |l| l["position"] }
|
88
|
+
lessons
|
89
|
+
else
|
90
|
+
lesson = list.find { |l| l["position"] == pos }
|
91
|
+
client_data["completed"] += [pos]
|
92
|
+
lesson
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def latest_version?
|
97
|
+
return true unless ClientData.exists?
|
98
|
+
return true if client_data["last_version_check"] &&
|
99
|
+
client_data["last_version_check"] >= Time.now - (60 * 60)
|
100
|
+
|
101
|
+
begin
|
102
|
+
latest = gateway.latest_version?
|
103
|
+
rescue
|
104
|
+
return true
|
105
|
+
end
|
106
|
+
|
107
|
+
client_data["last_version_check"] = if latest
|
108
|
+
Time.now
|
109
|
+
else
|
110
|
+
false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def setup?
|
115
|
+
return false unless ClientData.exists?
|
116
|
+
client_data["key"]
|
117
|
+
end
|
118
|
+
|
119
|
+
def directories_ready?
|
120
|
+
["video", "quiz", "lab", "text", "cgrp"].all? do |path|
|
121
|
+
File.directory?(path)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def download_and_extract(content)
|
126
|
+
location = content["style"] + "/" + content["s3_key"]
|
127
|
+
unless File.exist?(location)
|
128
|
+
gateway.download_content(content, folder: content["style"])
|
129
|
+
extract_content(content) if content["s3_key"].end_with?(".tar.gz")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def complete(position)
|
134
|
+
if client_data["completed"]
|
135
|
+
# we actually have to put the _next_ lesson on the completed stack
|
136
|
+
set_progress(self.next["position"])
|
137
|
+
else
|
138
|
+
client_data["completed"] = []
|
139
|
+
set_progress(position)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def current_position
|
146
|
+
@current_position ||= client_data["completed"]&.last || 0
|
147
|
+
end
|
148
|
+
|
149
|
+
def client_data
|
150
|
+
@client_data ||= ClientData.new
|
151
|
+
end
|
152
|
+
|
153
|
+
def extract_content(content)
|
154
|
+
folder = content["style"]
|
155
|
+
`tar -C #{folder} -xvzf #{folder}/#{content["s3_key"]}`
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|