skadategems-dev 0.0.9

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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.travis.yml +12 -0
  4. data/Gemfile +13 -0
  5. data/Guardfile +8 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +74 -0
  8. data/Rakefile +6 -0
  9. data/bin/skadate +9 -0
  10. data/bin/skadategems-dev +18 -0
  11. data/integration/README.md +97 -0
  12. data/integration/cli/skadate_init_spec.rb +86 -0
  13. data/integration/cli/skadate_spec.rb +50 -0
  14. data/integration/cli/spec_helper.rb +23 -0
  15. data/integration/lib/remote/download_file_spec.rb +136 -0
  16. data/integration/spec_helper.rb +101 -0
  17. data/lib/skadategems/dev/bundle.rb +115 -0
  18. data/lib/skadategems/dev/cli/remote.rb +364 -0
  19. data/lib/skadategems/dev/cli/skadate.rb +184 -0
  20. data/lib/skadategems/dev/remote.rb +170 -0
  21. data/lib/skadategems/dev/remote/clone_logger.rb +118 -0
  22. data/lib/skadategems/dev/remote/configs.rb +97 -0
  23. data/lib/skadategems/dev/remote/directory.rb +88 -0
  24. data/lib/skadategems/dev/remote/file.rb +157 -0
  25. data/lib/skadategems/dev/remote/includes/mysql_connect.php +8 -0
  26. data/lib/skadategems/dev/skadate.rb +30 -0
  27. data/lib/skadategems/dev/software_version.rb +66 -0
  28. data/lib/skadategems/dev/source_controller.rb +101 -0
  29. data/lib/skadategems/dev/version.rb +5 -0
  30. data/skadategems-dev.gemspec +33 -0
  31. data/spec/skadategems/dev/bundle_spec.rb +268 -0
  32. data/spec/skadategems/dev/cli/remote_spec.rb +623 -0
  33. data/spec/skadategems/dev/cli/skadate_spec.rb +182 -0
  34. data/spec/skadategems/dev/remote/clone_logger_spec.rb +324 -0
  35. data/spec/skadategems/dev/remote/directory_spec.rb +117 -0
  36. data/spec/skadategems/dev/remote/file_spec.rb +74 -0
  37. data/spec/skadategems/dev/remote_spec.rb +95 -0
  38. data/spec/skadategems/dev/skadate_spec.rb +62 -0
  39. data/spec/skadategems/dev/software_version_spec.rb +92 -0
  40. data/spec/skadategems/dev/source_controller_spec.rb +243 -0
  41. data/spec/spec_helper.rb +32 -0
  42. metadata +153 -0
@@ -0,0 +1,136 @@
1
+ require File.expand_path('../../spec_helper', __dir__)
2
+ require 'skadategems/dev/skadate'
3
+ require 'fileutils'
4
+ require 'digest/md5'
5
+
6
+ module SkadateGems::Dev
7
+ describe Remote::File do
8
+ let(:skadate) { Skadate.new(skadate_local_dir) }
9
+
10
+ let(:big_file_path) do
11
+ ENV['SKADATE_BIG_FILE_PATH'] || File.join(skadate_source_dir, 'install/skadate9.sql')
12
+ end
13
+
14
+ let(:big_file_name) { File.basename(big_file_path) }
15
+
16
+ let(:remote_file) do
17
+ Remote::File.new(skadate.remote, big_file_name, File.size(big_file_path))
18
+ end
19
+
20
+ before(:each) do
21
+ remote_install
22
+
23
+ symlink = File.join(skadate_remote_dir, big_file_name)
24
+ FileUtils.ln_s(big_file_path, symlink, verbose: verbose?)
25
+
26
+ FileUtils.cd(skadate_local_dir, verbose: verbose?)
27
+ end
28
+
29
+ describe '#request' do
30
+
31
+ it 'yields `http response` that allows us to #read_body as a stream' do
32
+ segments_count = 0
33
+
34
+ remote_file.request do |response|
35
+ response.read_body { |_| segments_count += 1 }
36
+ end
37
+
38
+ puts "[segments count = #{segments_count}]" if verbose?
39
+ expect(segments_count).to be > 100
40
+ end
41
+
42
+ context 'when :md5_checksum is given' do
43
+
44
+ context "and remote file's checksum is the same" do
45
+ let(:checksum) { Digest::MD5.file(big_file_path).hexdigest }
46
+
47
+ it 'responds with [304 "Not Modified"] and empty body' do
48
+ remote_file.request(md5_checksum: checksum) do |response|
49
+ expect(response).to be_a Net::HTTPNotModified
50
+ response.read_body { |_| raise 'should not have been called!' }
51
+ end
52
+ end
53
+ end
54
+
55
+ context "but remote file's checksum is different" do
56
+ let(:checksum) { 'a' * 32 }
57
+
58
+ it 'responds with [200 "OK"] and file content as a stream' do
59
+ segments_count = 0
60
+
61
+ remote_file.request(md5_checksum: checksum) do |response|
62
+ expect(response).to be_a Net::HTTPOK
63
+ response.read_body { |_| segments_count += 1 }
64
+ end
65
+
66
+ puts "[segments count = #{segments_count}]" if verbose?
67
+ expect(segments_count).to be > 100
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+
75
+ describe '#save_as(filename)' do
76
+ let(:local_file_path) { File.join(skadate_local_dir, big_file_name) }
77
+
78
+ context 'when there is no local file with a given filename' do
79
+ it 'downloads and saves a remote file' do
80
+ return_value = nil
81
+
82
+ expect {
83
+ return_value = remote_file.save_as(local_file_path)
84
+ }.to change { File.exists?(local_file_path) }.to true
85
+
86
+ expect(return_value).to be_true
87
+
88
+ expect(FileUtils.identical?(big_file_path, local_file_path)).to be_true
89
+ end
90
+ end
91
+
92
+ context 'when a given file is already exists' do
93
+
94
+ context 'and files are the same' do
95
+ before(:each) do
96
+ FileUtils.cp(big_file_path, local_file_path, verbose: verbose?)
97
+ expect(FileUtils.identical?(big_file_path, local_file_path)).to be_true
98
+ end
99
+
100
+ it "doesn't download a remote file" do
101
+ FileUtils.chmod('u-w', local_file_path, verbose: verbose?)
102
+ expect(File.writable?(local_file_path)).to be_false
103
+
104
+ return_value = nil
105
+
106
+ expect {
107
+ return_value = remote_file.save_as(local_file_path)
108
+ }.to_not change { File.writable?(local_file_path) }
109
+
110
+ expect(return_value).to eq false
111
+ end
112
+ end
113
+
114
+ context 'but the files are different' do
115
+ before(:each) do
116
+ File.write(local_file_path, 'Oh Noo!')
117
+ end
118
+
119
+ it 'downloads and rewrites a local file' do
120
+ return_value = nil
121
+
122
+ expect {
123
+ return_value = remote_file.save_as(local_file_path)
124
+ }.to change {
125
+ FileUtils.identical?(local_file_path, big_file_path)
126
+ }.to true
127
+
128
+ expect(return_value).to be_true
129
+ end
130
+ end
131
+
132
+ end
133
+ end
134
+
135
+ end
136
+ end
@@ -0,0 +1,101 @@
1
+ # integration/spec_helper.rb
2
+
3
+ require File.expand_path('../spec/spec_helper', __dir__)
4
+ require 'skadategems/dev/cli/skadate'
5
+ require 'fileutils'
6
+
7
+ RSpec.configure do |config|
8
+
9
+ config.before(:each) do
10
+ # cleanup sandbox directories
11
+ [skadate_local_dir, skadate_remote_dir].each do |entry|
12
+ if entry.is_a?(String) && (entry['tmp'] || entry['temp']) && entry['skadate']
13
+ FileUtils.rm_r Dir.glob("#{entry}/{*,.dev,.ht*}"), :verbose => verbose?
14
+ else
15
+ raise <<-ERRORMSG
16
+ Error: bad tmp dir entry "#{path}".
17
+ Explanation: by security reason tmp directories must contain 'tmp' and 'skadate' keywords in their pathname.
18
+ ERRORMSG
19
+ end
20
+ end
21
+ end
22
+
23
+ def env_var!(name)
24
+ ENV[name] or raise "`#{name}` is not defined"
25
+ end
26
+
27
+ def verbose?
28
+ (verbose = ENV['VERBOSE']) && verbose != '' && verbose.upcase != 'FALSE'
29
+ end
30
+
31
+ def skadate_tmp_dir
32
+ @skadate_tmp_dir ||= File.join(File.symlink?('/tmp') ? "/#{File.readlink('/tmp')}" : '/tmp', 'skadate')
33
+ end
34
+
35
+ def skadate_source_dir
36
+ @skadate_source_dir ||= env_var! 'SKADATE_SOURCE_DIR'
37
+ end
38
+
39
+ def skadate_local_dir
40
+ @skadate_local_dir ||= ENV['SKADATE_LOCAL_DIR'] || File.join(skadate_tmp_dir, 'local')
41
+ end
42
+
43
+ def skadate_remote_dir
44
+ @skadate_remote_dir ||= ENV['SKADATE_REMOTE_DIR'] || File.join(skadate_tmp_dir, 'remote')
45
+ end
46
+
47
+ def skadate_local_host
48
+ ENV['SKADATE_LOCAL_HOST'] || 'skadate-local-test'
49
+ end
50
+
51
+ def skadate_remote_host
52
+ ENV['SKADATE_REMOTE_HOST'] || 'skadate-remote-test'
53
+ end
54
+
55
+ def remote_install(options = {})
56
+ FileUtils.cd(skadate_remote_dir, :verbose => verbose?) do
57
+ FileUtils.mkdir('internals', :verbose => verbose?)
58
+
59
+ puts '[...] > internals/config.php' if verbose?
60
+ IO.write('internals/config.php', <<-CONFIGPHP)
61
+ <?php
62
+ define('DIR_SITE_ROOT', '#{skadate_remote_dir}/');
63
+ define('SITE_URL', 'http://#{skadate_remote_host}/');
64
+ CONFIGPHP
65
+ end
66
+
67
+ FileUtils.cd(skadate_local_dir, :verbose => verbose?) do
68
+ puts "skadate init -r http://#{skadate_remote_host}/" if verbose?
69
+ SkadateGems::Dev::CLI::Skadate.start ['init', '-r', "http://#{skadate_remote_host}/"]
70
+ FileUtils.cp('exec.php', skadate_remote_dir, :verbose => verbose?)
71
+ end
72
+ end
73
+
74
+ def local_install(options = {})
75
+ end
76
+
77
+ def source_copy(source_file_paths, destination_root = :local, options = {})
78
+ destination_root = skadate_local_dir if destination_root == :local
79
+ destination_root = skadate_remote_dir if destination_root == :remote
80
+
81
+ [*source_file_paths].each do |entry|
82
+ src = File.join(skadate_source_dir, entry)
83
+ dest = File.join(destination_root, entry)
84
+ FileUtils.mkdir_p File.dirname(dest), :verbose => verbose?
85
+ FileUtils.cp(src, dest, options.merge(:verbose => verbose?))
86
+ end
87
+ end
88
+
89
+ # @param bytes [Fixnum] value in bytes
90
+ # @return [String] human readable format
91
+ def human_readable_bytes(bytes)
92
+ units = %W(B KiB MiB GiB TiB)
93
+
94
+ size, unit = units.reduce(bytes.to_f) do |(fsize, _), type|
95
+ fsize > 512 ? [fsize / 1024, type] : (break [fsize, type])
96
+ end
97
+
98
+ "#{size > 9 || size.modulo(1) < 0.1 ? '%d' : '%.1f'} %s" % [size, unit]
99
+ end
100
+
101
+ end
@@ -0,0 +1,115 @@
1
+ require 'rubygems'
2
+ require 'rubygems/package'
3
+ require 'zlib'
4
+ require 'fileutils'
5
+
6
+ module SkadateGems
7
+ module Dev
8
+
9
+ class Bundle
10
+
11
+ attr_accessor :layout_theme
12
+
13
+ # Bundler instance initializer.
14
+ #
15
+ # @param skadate [Skadate]
16
+ def initialize(skadate)
17
+ @source = skadate.source
18
+ @internal_c = @source.internal_c
19
+ @external_c = @source.external_c
20
+ @userfiles = @source.userfiles
21
+ @themes_dir = @source.filename('layout/themes/')
22
+ end
23
+
24
+ # Filter files by their filename.
25
+ #
26
+ # @param filename [String] absolute path to a file
27
+ # @return [boolean] true if given filename should be bundled
28
+ def should_include?(filename)
29
+ return false if filename.start_with?(@internal_c, @external_c, @userfiles) ||
30
+ filename.end_with?('.tar', '.gz', '.zip', '.swp', '~', 'Thumbs.db', 'desktop.ini')
31
+
32
+ if filename.start_with?(@themes_dir) && layout_theme &&
33
+ !filename.start_with?(File.join(@themes_dir, layout_theme))
34
+ return false
35
+ end
36
+
37
+ true
38
+ end
39
+
40
+ # Create a tar file in memory.
41
+ #
42
+ # @return [StringIO] whose underlying String is the contents of the tar file
43
+ def tar
44
+ return @tar if @tar
45
+
46
+ string_io = StringIO.new('')
47
+ Gem::Package::TarWriter.new(string_io) do |tar|
48
+
49
+ htaccess = File.join(@source.root, '.htaccess')
50
+ if File.exists?(htaccess)
51
+ tar.add_file '.htaccess', 0644 do |tar_file|
52
+ File.open(htaccess, 'rb') { |file| tar_file.write(file.read) }
53
+ end
54
+ end
55
+
56
+ Dir[File.join(@source.root, '**/*')].each do |filename|
57
+ next unless should_include?(filename)
58
+
59
+ if File.directory?(filename)
60
+ tar.mkdir (relative_path filename), 0755
61
+
62
+ htaccess = File.join(filename, '.htaccess')
63
+ if File.exists?(htaccess)
64
+ filename = htaccess # iteration continues...
65
+ else
66
+ next
67
+ end
68
+ end
69
+
70
+ tar.add_file (relative_path filename), 0644 do |tar_file|
71
+ File.open(filename, 'rb') { |file| tar_file.write(file.read) }
72
+ end
73
+ end
74
+
75
+ [ (int_c = relative_path @internal_c),
76
+ "#{int_c}/cache",
77
+ "#{int_c}/components",
78
+ "#{int_c}/forms",
79
+ "#{int_c}/lang",
80
+ relative_path(@external_c),
81
+ relative_path(@userfiles),
82
+ ].each { |dir| tar.mkdir dir, 0777 }
83
+ end
84
+
85
+ string_io.rewind
86
+ @tar = string_io
87
+ end
88
+
89
+ # gzips the underlying string in the given `StringIO`,
90
+ # returning a new `StringIO` representing the compressed file.
91
+ #
92
+ # @param filename [String] optional output filename
93
+ # @return [StringIO|Fixnum]
94
+ def gzip(filename = nil)
95
+ gz = StringIO.new('')
96
+ z = Zlib::GzipWriter.new(gz)
97
+ z.write(tar.string)
98
+ z.close # this is necessary!
99
+
100
+ if filename
101
+ File.binwrite(filename, gz.string)
102
+ else
103
+ StringIO.new(gz.string)
104
+ end
105
+ end
106
+
107
+ private
108
+ def relative_path(filename)
109
+ filename.sub(/^#{Regexp.escape(@source.root)}\/?/, '')
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,364 @@
1
+ require 'time'
2
+ require 'thor'
3
+ require 'json'
4
+ require 'skadategems/dev/skadate'
5
+ require 'skadategems/dev/remote/clone_logger'
6
+ require 'ruby-progressbar'
7
+
8
+ module SkadateGems::Dev
9
+ module CLI
10
+
11
+ class Remote < Thor
12
+ namespace 'remote'
13
+
14
+ desc 'ping', 'Send ping request to a remote application'
15
+ long_desc <<-LONGDESC
16
+ Send ping request to a remote server's `exec.php` script. \x5
17
+ \x5
18
+ \x5 Example usage: \x5
19
+ \x5
20
+ \x5 $> skadate remote ping
21
+ \x5
22
+ \x5
23
+ LONGDESC
24
+
25
+ def ping
26
+ accessor = current.remote.accessor
27
+ script = ExecPHP::ScriptBatch.new { |s| s << 'echo "pong!";' }
28
+
29
+ say '=> '
30
+ say accessor.execphp_uri, :cyan
31
+
32
+ accessor.exec(script) do |res|
33
+ say '#> '
34
+ say "#{res.code} [#{res.message}]", (res.code == '200' ? :green : :red)
35
+
36
+ if res.body.empty?
37
+ say 'Error: empty response body', :red
38
+ elsif res.body != 'pong!'
39
+ say 'Error: wrong response body:', :red
40
+ if res.body.length > 1000
41
+ say res.body[0..984], nil, false
42
+ say ' ... (continued)', :yellow
43
+ else
44
+ say '"' + res.body + '"'
45
+ end
46
+ else
47
+ say '>> '
48
+ say res.body, :white
49
+ end
50
+ end
51
+ end
52
+
53
+ desc 'exec FILENAME', 'Execute php scripts remotely'
54
+ long_desc <<-LONGDESC
55
+ Send a given php file to a remote application server for execution.
56
+
57
+ Example usage:
58
+ \x5
59
+ \x5 $> skadate remote exec path/to/my_script.php
60
+ \x5
61
+ \x5
62
+ LONGDESC
63
+
64
+ def exec(filename)
65
+ puts current.remote.exec { |batch| batch.include_file(filename) }.body
66
+ end
67
+
68
+ desc 'config ID [VALUE]', 'Manage remote application configuration'
69
+
70
+ option :type, :type => :string,
71
+ :desc => 'Explicitly specify config value type'
72
+
73
+ def config(key_or_id, value = nil)
74
+ key_or_id = key_or_id.to_i if numeric? key_or_id
75
+ config = current.remote.configs[key_or_id]
76
+
77
+ say '>> ', (:yellow unless value.nil?), false
78
+ say "##{config.id} ", :white, false
79
+ say '| '
80
+ say config.name, :cyan, false
81
+ say ': '
82
+ say config.value.to_json, (color_for config.value), value.nil?
83
+
84
+ unless value.nil?
85
+ value =
86
+ case options[:type]
87
+ when nil
88
+ if value == 'true'
89
+ true
90
+ elsif value == 'false'
91
+ false
92
+ elsif numeric? value
93
+ value.to_i
94
+ else
95
+ value
96
+ end
97
+ when 'str'
98
+ when 'string'
99
+ value
100
+ when 'int'
101
+ when 'integer'
102
+ when 'number'
103
+ value.to_i
104
+ when 'bool'
105
+ when 'boolean'
106
+ value != 'false' && value != '0'
107
+ when 'json'
108
+ JSON.parse(value)
109
+ else
110
+ raise "unrecognized config value type '#{options[:type]}'"
111
+ end
112
+
113
+ say ' => ', :white, false
114
+ say value.to_json, (color_for value)
115
+
116
+ say '>> '
117
+ if yes?('perform this update? [yn]')
118
+ config.value = value
119
+
120
+ case config.update_status
121
+ when :updated
122
+ say '[updated]', :green
123
+ when :not_modified
124
+ say '[not modified]', :yellow
125
+ else
126
+ say '[error]', :red
127
+ end
128
+ else
129
+ say '[cancelled]', :red
130
+ end
131
+ end
132
+ end
133
+
134
+ desc 'clone', 'Clone production-running application'
135
+ long_desc <<-LONGDESC
136
+ Clone production-running application locally for development purposes.
137
+
138
+ To download remote application source files:
139
+ \x5
140
+ \x5 $> skadate remote clone --sources \x5
141
+ \x5
142
+
143
+ To get a remote application database dump:
144
+ \x5
145
+ \x5 $> skadate remote clone --database \x5
146
+ \x5
147
+
148
+ Or if you want to make a full clone of a running application including all the data and user uploaded files:
149
+ \x5
150
+ \x5 $> skadate remote clone --sources --database --userfiles \x5
151
+ \x5
152
+
153
+ Make a note that by default `remote clone --sources` copies only one `layout/theme` that is currently active on a remote server.
154
+ If you want to get another theme specify it using the `--layout-theme` switch as following:
155
+ \x5
156
+ \x5 $> skadate remote clone --layout-theme stencil \x5
157
+ \x5
158
+ \x5
159
+ LONGDESC
160
+
161
+ option :sources, :type => :boolean,
162
+ :desc => 'Copy application source files'
163
+
164
+ option :schema, :type => :boolean,
165
+ :desc => 'Copy database schema'
166
+
167
+ option :configs, :type => :boolean,
168
+ :desc => 'Copy application-related configs data'
169
+
170
+ option :database, :type => :boolean,
171
+ :desc => 'Copy the whole application database'
172
+
173
+ option :userfiles, :type => :boolean,
174
+ :desc => 'Copy `userfiles` directory contents'
175
+
176
+ option :layout_theme, :type => :string,
177
+ :banner => 'NAME',
178
+ :desc => 'Force layout theme'
179
+
180
+ IGNORED_DIR_PATHS = %w[
181
+ /$external_c /$internal_c /$userfiles /$userfiles/tmp
182
+ /external_c /internal_c /userfiles /userfiles/tmp
183
+ /cgi-bin
184
+ /layout/themes
185
+ /m/application/cache/smarty_compile
186
+ ]
187
+
188
+ IGNORED_FILE_EXTENSIONS = %w[.tar .bz2 .gz .zip .rar .iso]
189
+
190
+ # default values, rewritable by ENV['SKADATE_MAX_*_FILE_SIZE']
191
+ MAX_SOURCE_FILE_SIZE = 1024 * 1024 * 2 # ~2 megabytes
192
+ MAX_USER_FILE_SIZE = 1024 * 1024 * 10 # ~10 megabytes
193
+
194
+ # show progressbar when downloading file size exceeds this value
195
+ SHOW_PROGRESS_FILE_SIZE = 1024 * 512 # ~512 kilobytes
196
+
197
+ def clone
198
+ if options[:schema] || options[:configs]
199
+ say <<-STDOUT, :red
200
+ Remote clone options `--schema`, `--configs` are currently not implemented, please use `--database` for now.
201
+ STDOUT
202
+ return
203
+ end
204
+
205
+ if options.size == 0
206
+ say 'Error: `remote clone` options are required', :red
207
+ return help('clone')
208
+ end
209
+
210
+ remote = current.remote
211
+ layout_theme = options[:layout_theme]
212
+ @max_file_size = MAX_SOURCE_FILE_SIZE
213
+
214
+ dev_logs_dir = ::File.join(current.root_dir, '.dev', 'log')
215
+ FileUtils.mkdir_p(dev_logs_dir) unless ::File.exists?(dev_logs_dir)
216
+ log_filename = ::File.join(dev_logs_dir, "remote-clone[#{Time.now.to_i}].log")
217
+
218
+ logger.start(log_filename) do |log|
219
+ if options[:sources]
220
+ log.puts '>> Downloading application sources...', :yellow
221
+ download_source_dir(remote.root, log)
222
+
223
+ if layout_theme.nil?
224
+ log.puts('>> Getting active theme name...', :yellow)
225
+ config = remote.configs[1]
226
+ raise <<-ERRMSG unless config.name == 'theme'
227
+ unexpected remote application config entry `#{config.name}`, (expected `theme` [config_id: 1])
228
+ ERRMSG
229
+ layout_theme = config.value
230
+ end
231
+ end
232
+
233
+ if layout_theme
234
+ log.puts ">> Downloading [/layout/themes/#{layout_theme}/*]...", :yellow
235
+ download_source_dir remote.directory("/layout/themes/#{layout_theme}"), log
236
+ end
237
+
238
+ if options[:database]
239
+ log.puts '>> Creating remote database dump...', :yellow
240
+ remote.create_mysql_dump do |remote_file|
241
+ local_filename = File.join(current.root_dir,
242
+ "remote-db-clone.#{remote_file.basename =~ /\.tar\.gz$/ ? 'tar.gz' : 'sql'}")
243
+
244
+ if log.mute?
245
+ remote_file.save_as(local_filename)
246
+ else
247
+ progressbar = nil
248
+ remote_file.save_as(local_filename) do |chunk_size|
249
+ if progressbar
250
+ progressbar.progress += chunk_size
251
+ else
252
+ progressbar = create_progressbar(remote_file, log.padding, chunk_size)
253
+ end
254
+ end
255
+ end
256
+
257
+ log.puts '>> Remote cache cleanup...', :yellow
258
+ end ? log.puts('>> Done!', :green) : log.puts('>> Warning: remote cache cleanup failed', :red)
259
+ end
260
+
261
+ if options[:userfiles]
262
+ log.puts '>> Detecting `$userfiles` directory...', :yellow
263
+ @max_file_size = MAX_USER_FILE_SIZE
264
+ download_source_dir(remote.userfiles, log)
265
+ end
266
+
267
+ log.puts '>> Completed!', :green
268
+ end
269
+ end
270
+
271
+ protected
272
+ def current
273
+ @current ||= SkadateGems::Dev::Skadate.new(Dir.pwd)
274
+ end
275
+
276
+ def numeric?(string)
277
+ string =~ /\A\d+\z/
278
+ end
279
+
280
+ def color_for(value)
281
+ if value.is_a? String
282
+ :green
283
+ elsif value.is_a? Fixnum
284
+ :blue
285
+ elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
286
+ :magenta
287
+ elsif value.is_a?(Array) || value.is_a?(Hash)
288
+ :yellow
289
+ else
290
+ :red
291
+ end
292
+ end
293
+
294
+ def logger
295
+ SkadateGems::Dev::Remote::CloneLogger
296
+ end
297
+
298
+ # @param directory [Remote::Directory] remote directory entry
299
+ # @param log [Remote::CloneLogger] logger instance
300
+ def download_source_dir(directory, log)
301
+ list = nil
302
+ log.entry(directory) { list = directory.list }
303
+ return unless list
304
+
305
+ log.padding += 1
306
+
307
+ begin
308
+ list.each do |entry|
309
+ if entry.directory?
310
+ if IGNORED_DIR_PATHS.include?(entry.path)
311
+ log.entry(entry, :skipped, 'skipped by pathname') { }
312
+ else
313
+ download_source_dir(entry, log)
314
+ end
315
+ else
316
+ if IGNORED_FILE_EXTENSIONS.include?(entry.extname)
317
+ log.entry(entry, :skipped, "skipped by (*#{entry.extname})") { }
318
+ elsif entry.size > @max_file_size
319
+ log.entry(entry, :skipped, 'skipped by filesize') { }
320
+ else
321
+ log.entry(entry) do
322
+ local_filename = File.join(current.root_dir, entry.path)
323
+
324
+ downloaded =
325
+ if !log.mute? && entry.size > SHOW_PROGRESS_FILE_SIZE
326
+ progressbar = nil
327
+ entry.save_as(local_filename) do |chunk_size|
328
+ if progressbar
329
+ progressbar.progress += chunk_size
330
+ else
331
+ progressbar = create_progressbar(entry, log.padding, chunk_size)
332
+ end
333
+ end
334
+ else
335
+ entry.save_as(local_filename)
336
+ end
337
+
338
+ downloaded ? :downloaded : :unchanged
339
+ end
340
+ end
341
+ end
342
+ end
343
+ ensure
344
+ log.padding -= 1
345
+ end
346
+ end
347
+
348
+ def create_progressbar(entry, padding, starting_at = 0)
349
+ format =
350
+ "#{' ' * padding}~> %t (#{set_color(entry.friendly_size, :cyan)}) [#{set_color('%p%%', :green)}] [#{set_color('%B', :green)}] %e "
351
+
352
+ ProgressBar.create title: entry.basename,
353
+ total: entry.size,
354
+ smoothing: 0.5,
355
+ throttle_rate: 0.3,
356
+ format: format,
357
+ progress_mark: '·',
358
+ starting_at: starting_at
359
+ end
360
+
361
+ end
362
+
363
+ end
364
+ end