u3d 0.9.3 → 0.9.4

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.
@@ -21,11 +21,12 @@
21
21
  ## --- END LICENSE BLOCK ---
22
22
 
23
23
  require 'net/http'
24
- require 'u3d/iniparser'
25
24
  require 'u3d/utils'
25
+ require 'u3d/download_validator'
26
26
 
27
27
  module U3d
28
28
  # Take care of downloading files and packages
29
+ # rubocop:disable ModuleLength
29
30
  module Downloader
30
31
  # Name of the directory for the package downloading
31
32
  DOWNLOAD_DIRECTORY = 'Unity_Packages'.freeze
@@ -35,30 +36,98 @@ module U3d
35
36
  UNITY_MODULE_FILE_REGEX = %r{\/([\w\-_\.\+]+\.(?:pkg|exe|zip|sh|deb))}
36
37
 
37
38
  class << self
38
- def hash_validation(expected: nil, actual: nil)
39
- if expected
40
- if expected != actual
41
- UI.verbose "Expected hash is #{expected}, file hash is #{actual}"
42
- UI.important 'File looks corrupted (wrong hash)'
43
- return false
39
+ # fetch modules and put them in local cache
40
+ def fetch_modules(definition, packages: [], download: nil)
41
+ if download
42
+ download_modules(definition, packages: packages)
43
+ else
44
+ local_files(definition, packages: packages)
45
+ end
46
+ end
47
+
48
+ # download packages
49
+ def download_modules(definition, packages: [])
50
+ files = []
51
+ validator, downloader = setup_os(definition.os)
52
+
53
+ packages.each do |package|
54
+ get_package(downloader, validator, package, definition, files)
55
+ end
56
+
57
+ # On Linux, make sure the files are executable
58
+ # FIXME: Move me to the LinuxInstaller
59
+ files.each { |f| U3dCore::CommandExecutor.execute(command: "chmod a+x #{f[1]}") } if definition.os == :linux
60
+
61
+ files
62
+ end
63
+
64
+ # find already downloaded packages
65
+ def local_files(definition, packages: [])
66
+ files = []
67
+ validator, downloader = setup_os(definition.os)
68
+
69
+ packages.each do |package|
70
+ path = downloader.destination_for(package, definition)
71
+ if File.file?(path)
72
+ if validator.validate(package, path, definition)
73
+ files << [package, path, definition[package]]
74
+ else
75
+ UI.important "File present at #{path} is not correct, will not be used. Skipping #{package}"
76
+ end
77
+ else
78
+ UI.error "No file has been downloaded for #{package}, or it has been moved from #{path}"
44
79
  end
80
+ end
81
+
82
+ files
83
+ end
84
+
85
+ private #-----------------------------------------------------------------
86
+
87
+ def setup_os(os)
88
+ case os
89
+ when :linux
90
+ validator = LinuxValidator.new
91
+ downloader = Downloader::LinuxDownloader.new
92
+ when :mac
93
+ validator = MacValidator.new
94
+ downloader = Downloader::MacDownloader.new
95
+ when :win
96
+ validator = WindowsValidator.new
97
+ downloader = Downloader::WindowsDownloader.new
45
98
  else
46
- UI.verbose 'No hash validation available. File is assumed correct but may not be.'
99
+ raise ArgumentError, "Operating system #{os.id2name} is not recognized"
47
100
  end
48
- true
101
+ return validator, downloader
49
102
  end
50
103
 
51
- def size_validation(expected: nil, actual: nil)
52
- if expected
53
- if expected != actual
54
- UI.verbose "Expected size is #{expected}, file size is #{actual}"
55
- UI.important 'File looks corrupted (wrong size)'
56
- return false
104
+ def get_package(downloader, validator, package, definition, files)
105
+ path = downloader.destination_for(package, definition)
106
+ url = downloader.url_for(package, definition)
107
+ if File.file?(path)
108
+ UI.verbose "Installer file for #{package} seems to be present at #{path}"
109
+ if validator.validate(package, path, definition)
110
+ UI.message "#{package.capitalize} is already downloaded"
111
+ files << [package, path, definition[package]]
112
+ return
113
+ else
114
+ extension = File.extname(path)
115
+ new_path = File.join(File.dirname(path), File.basename(path, extension) + '_CORRUPTED' + extension)
116
+ UI.important "File present at #{path} is not correct, it has been renamed to #{new_path}"
117
+ File.rename(path, new_path)
57
118
  end
119
+ end
120
+
121
+ UI.header "Downloading #{package} version #{definition.version}"
122
+ UI.message 'Downloading from ' + url.to_s.cyan.underline
123
+ download_package(path, url, size: definition.size_in_kb(package))
124
+
125
+ if validator.validate(package, path, definition)
126
+ UI.success "Successfully downloaded #{package}."
127
+ files << [package, path, definition[package]]
58
128
  else
59
- UI.verbose 'No size validation available. File is assumed correct but may not be.'
129
+ UI.error "Failed to download #{package}"
60
130
  end
61
- true
62
131
  end
63
132
 
64
133
  def download_package(path, url, size: nil)
@@ -79,7 +148,8 @@ module U3d
79
148
  f.write(segment)
80
149
  current += segment.length
81
150
  # wait for Net::HTTP buffer on slow networks
82
- sleep 0.08 # adjust to reduce CPU
151
+ # FIXME revisits, this slows down download on fast network
152
+ # sleep 0.08 # adjust to reduce CPU
83
153
  next unless UI.interactive?
84
154
  next unless Time.now.to_f - last_print_update > 0.5
85
155
  last_print_update = Time.now.to_f
@@ -101,268 +171,46 @@ module U3d
101
171
  end
102
172
 
103
173
  class MacDownloader
104
- class << self
105
- # Downloads all packages available for given version
106
- def download_all(version, cached_versions)
107
- if cached_versions[version].nil?
108
- UI.error "No version #{version} was found in cache. It might need updating."
109
- return nil
110
- end
111
- files = []
112
- ini_file = INIparser.load_ini(version, cached_versions)
113
- ini_file.keys.each do |k|
114
- result = download_specific(k, version, cached_versions)
115
- files << [k, result[0], result[1]] unless result.nil?
116
- end
117
- files
118
- end
119
-
120
- # Downloads a specific package for given version
121
- def download_specific(package, version, cached_versions)
122
- if cached_versions[version].nil?
123
- UI.error "No version #{version} was found in cache. It might need updating."
124
- return nil
125
- end
126
-
127
- ini_file = INIparser.load_ini(version, cached_versions)
128
- if ini_file[package].empty?
129
- UI.error "No package \"#{package}\" was found for version #{version}."
130
- return nil
131
- end
132
-
133
- url = cached_versions[version]
134
- dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, version)
135
- Utils.ensure_dir(dir)
136
- return [get_package(package, ini_file, dir, url), ini_file[package]]
137
- end
138
-
139
- private #---------------------------------------------------------------
140
-
141
- def get_package(name, ini_file, main_dir, base_url)
142
- file_name = UNITY_MODULE_FILE_REGEX.match(ini_file[name]['url'])[1]
143
- file_path = File.expand_path(file_name, main_dir)
144
-
145
- # Check if file already exists and validate it
146
- if File.file?(file_path)
147
- if Downloader.size_validation(expected: ini_file[name]['size'], actual: File.size(file_path)) &&
148
- Downloader.hash_validation(expected: ini_file[name]['md5'], actual: Utils.hashfile(file_path))
149
- UI.important "#{name.capitalize} already downloaded at #{file_path}"
150
- return file_path
151
- else
152
- UI.verbose "Deleting existing file at #{file_path}"
153
- File.delete(file_path)
154
- end
155
- end
156
-
157
- # Download file
158
- url = base_url + ini_file[name]['url']
159
- UI.header "Downloading #{name}"
160
- UI.verbose 'Downloading from ' + url.to_s.cyan.underline
161
- Downloader.download_package(file_path, url, size: ini_file[name]['size'])
162
-
163
- # Validation download
164
- if Downloader.size_validation(expected: ini_file[name]['size'], actual: File.size(file_path)) &&
165
- Downloader.hash_validation(expected: ini_file[name]['md5'], actual: Utils.hashfile(file_path))
166
- UI.success "Successfully downloaded #{name}."
167
- else
168
- File.delete(file_path)
169
- raise 'Download failed: file is corrupted, deleting it.'
170
- end
171
-
172
- file_path
173
- end
174
-
175
- def all_local_files(version)
176
- files = []
177
- ini_file = INIparser.load_ini(version, {}, offline: true)
178
- ini_file.keys.each do |k|
179
- result = local_file(k, version)
180
- files << [k, result[0], result[1]] unless result.nil?
181
- end
182
- files
183
- end
184
-
185
- def local_file(package, version)
186
- ini_file = INIparser.load_ini(version, {}, offline: true)
187
- if ini_file[package].empty?
188
- UI.error "No package \"#{package}\" was found for version #{version}."
189
- return nil
190
- end
191
-
192
- dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, version)
193
- raise "Main directory #{dir} does not exist. Nothing has been downloaded for version #{version}" unless Dir.exist?(dir)
194
-
195
- file_name = UNITY_MODULE_FILE_REGEX.match(ini_file[package]['url'])[1]
196
- file_path = File.expand_path(file_name, dir)
174
+ def destination_for(package, definition)
175
+ dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, definition.version)
176
+ Utils.ensure_dir(dir)
177
+ file_name = UNITY_MODULE_FILE_REGEX.match(definition[package]['url'])[1]
197
178
 
198
- unless File.file?(file_path)
199
- UI.error "Package #{package} has not been downloaded"
200
- return nil
201
- end
202
-
203
- unless Downloader.size_validation(expected: ini_file[package]['size'], actual: File.size(file_path)) &&
204
- Downloader.hash_validation(expected: ini_file[package]['md5'], actual: Utils.hashfile(file_path))
205
- UI.error "File at #{file_path} is corrupted, deleting it"
206
- File.delete(file_path)
207
- return nil
208
- end
179
+ File.expand_path(file_name, dir)
180
+ end
209
181
 
210
- return [file_path, ini_file[package]]
211
- end
182
+ def url_for(package, definition)
183
+ definition.url + definition[package]['url']
212
184
  end
213
185
  end
214
186
 
215
187
  class LinuxDownloader
216
- class << self
217
- def download(version, cached_versions)
218
- if cached_versions[version].nil?
219
- UI.error "No version #{version} was found in cache. It might need updating."
220
- return nil
221
- end
222
- url = cached_versions[version]
223
- dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, version)
224
- Utils.ensure_dir(dir)
225
- file_name = UNITY_MODULE_FILE_REGEX.match(url)[1]
226
- file_path = File.expand_path(file_name, dir)
188
+ def destination_for(package, definition)
189
+ dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, definition.version)
190
+ Utils.ensure_dir(dir)
191
+ file_name = UNITY_MODULE_FILE_REGEX.match(definition[package]['url'])[1]
227
192
 
228
- # Check if file already exists
229
- # Note: without size or hash validation, the file is assumed to be correct
230
- if File.file?(file_path)
231
- UI.important "File already downloaded at #{file_path}"
232
- return file_path
233
- end
234
-
235
- # Download file
236
- UI.header "Downloading Unity #{version}"
237
- UI.verbose 'Downloading from ' + url.to_s.cyan.underline
238
- Downloader.download_package(file_path, url)
239
- U3dCore::CommandExecutor.execute(command: "chmod a+x #{file_path}")
240
- file_path
241
- end
193
+ File.expand_path(file_name, dir)
194
+ end
242
195
 
243
- def local_file(version)
244
- dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, version)
245
- raise "Main directory #{dir} does not exist. Nothing has been downloaded for version #{version}" unless Dir.exist?(dir)
246
- find_cmd = "find #{dir}/ -maxdepth 2 -name '*.sh'"
247
- files = U3dCore::CommandExecutor.execute(command: find_cmd).split("\n")
248
- return files[0] unless files.empty?
249
- raise 'No file has been downloaded'
250
- end
196
+ def url_for(_package, definition)
197
+ definition.url
251
198
  end
252
199
  end
253
200
 
254
201
  class WindowsDownloader
255
- class << self
256
- def download_all(version, cached_versions)
257
- if cached_versions[version].nil?
258
- UI.error "No version #{version} was found in cache. It might need updating."
259
- return nil
260
- end
261
- files = []
262
- ini_file = INIparser.load_ini(version, cached_versions)
263
- ini_file.keys.each do |k|
264
- result = download_specific(k, version, cached_versions)
265
- files << [k, result[0], result[1]] unless result.nil?
266
- end
267
- files
268
- end
269
-
270
- # Downloads a specific package for given version
271
- def download_specific(package, version, cached_versions)
272
- if cached_versions[version].nil?
273
- UI.error "No version #{version} was found in cache. It might need updating."
274
- return nil
275
- end
276
-
277
- ini_file = INIparser.load_ini(version, cached_versions)
278
- if ini_file[package].empty?
279
- UI.error "No package \"#{package}\" was found for version #{version}."
280
- return nil
281
- end
202
+ def destination_for(package, definition)
203
+ dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, definition.version)
204
+ Utils.ensure_dir(dir)
205
+ file_name = UNITY_MODULE_FILE_REGEX.match(definition[package]['url'])[1]
282
206
 
283
- url = cached_versions[version]
284
- dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, version)
285
- Utils.ensure_dir(dir)
286
- return [get_package(package, ini_file, dir, url), ini_file[package]]
287
- end
288
-
289
- def all_local_files(version)
290
- files = []
291
- ini_file = INIparser.load_ini(version, {}, offline: true)
292
- ini_file.keys.each do |k|
293
- result = local_file(k, version)
294
- files << [k, result[0], result[1]] unless result.nil?
295
- end
296
- files
297
- end
298
-
299
- def local_file(package, version)
300
- ini_file = INIparser.load_ini(version, {}, offline: true)
301
- if ini_file[package].empty?
302
- UI.error "No package \"#{package}\" was found for version #{version}."
303
- return nil
304
- end
305
-
306
- dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, version)
307
- raise "Main directory #{dir} does not exist. Nothing has been downloaded for version #{version}" unless Dir.exist?(dir)
308
-
309
- file_name = UNITY_MODULE_FILE_REGEX.match(ini_file[package]['url'])[1]
310
- file_path = File.expand_path(file_name, dir)
311
-
312
- unless File.file?(file_path)
313
- UI.error "Package #{package} has not been downloaded"
314
- return nil
315
- end
316
-
317
- rounded_size = (File.size(file_path).to_f / 1024).floor
318
- unless Downloader.size_validation(expected: ini_file[package]['size'], actual: rounded_size) &&
319
- Downloader.hash_validation(expected: ini_file[package]['md5'], actual: Utils.hashfile(file_path))
320
- UI.error "File at #{file_path} is corrupted, deleting it"
321
- File.delete(file_path)
322
- return nil
323
- end
324
-
325
- return [file_path, ini_file[package]]
326
- end
327
-
328
- private #---------------------------------------------------------------
329
-
330
- def get_package(name, ini_file, main_dir, base_url)
331
- file_name = UNITY_MODULE_FILE_REGEX.match(ini_file[name]['url'])[1]
332
- file_path = File.expand_path(file_name, main_dir)
333
-
334
- # Check if file already exists and validate it
335
- if File.file?(file_path)
336
- rounded_size = (File.size(file_path).to_f / 1024).floor
337
- if Downloader.size_validation(expected: ini_file[name]['size'], actual: rounded_size) &&
338
- Downloader.hash_validation(expected: ini_file[name]['md5'], actual: Utils.hashfile(file_path))
339
- UI.important "File already downloaded at #{file_path}"
340
- return file_path
341
- else
342
- UI.verbose 'Deleting existing file'
343
- File.delete(file_path)
344
- end
345
- end
346
-
347
- # Download file
348
- url = base_url + ini_file[name]['url']
349
- UI.header "Downloading #{name}"
350
- UI.verbose 'Downloading from ' + url.to_s.cyan.underline
351
- Downloader.download_package(file_path, url, size: ini_file[name]['size'] * 1024)
352
-
353
- # Validation download
354
- rounded_size = (File.size(file_path).to_f / 1024).floor
355
- if Downloader.size_validation(expected: ini_file[name]['size'], actual: rounded_size) &&
356
- Downloader.hash_validation(expected: ini_file[name]['md5'], actual: Utils.hashfile(file_path))
357
- UI.success "Successfully downloaded #{name}."
358
- else
359
- File.delete(file_path)
360
- raise 'Download failed: file is corrupted, deleting it.'
361
- end
207
+ File.expand_path(file_name, dir)
208
+ end
362
209
 
363
- file_path
364
- end
210
+ def url_for(package, definition)
211
+ definition.url + definition[package]['url']
365
212
  end
366
213
  end
367
214
  end
215
+ # rubocop:enable ModuleLength
368
216
  end
@@ -30,25 +30,26 @@ module U3d
30
30
  #####################################################
31
31
  # @!group INI parameters to load and save ini files
32
32
  #####################################################
33
- INI_NAME = 'unity-%{version}-%{os}.ini'.freeze
33
+ INI_NAME = 'unity-%<version>s-%<os>s.ini'.freeze
34
34
  INI_LINUX_PATH = File.join(ENV['HOME'], '.u3d', 'ini_files').freeze
35
35
  INI_MAC_PATH = File.join(ENV['HOME'], 'Library', 'Application Support', 'u3d', 'ini_files').freeze
36
36
  INI_WIN_PATH = File.join(ENV['HOME'], 'AppData', 'Local', 'u3d', 'ini_files').freeze
37
37
 
38
38
  class << self
39
39
  def load_ini(version, cached_versions, os: U3dCore::Helper.operating_system, offline: false)
40
- unless os == :win || os == :mac
41
- raise ArgumentError, "OS #{os.id2name} does not use ini files"
42
- end
43
40
  os = if os == :mac
44
41
  'osx'
45
42
  else
46
43
  os.id2name
47
44
  end
48
- ini_name = INI_NAME % { version: version, os: os }
45
+ ini_name = format(INI_NAME, version: version, os: os)
49
46
  Utils.ensure_dir(default_ini_path)
50
47
  ini_path = File.expand_path(ini_name, default_ini_path)
51
48
  unless File.file?(ini_path)
49
+ if os == 'linux'
50
+ UI.error "No INI file for version #{version}. Try discovering the available versions with 'u3d available -f'"
51
+ return nil
52
+ end
52
53
  raise "INI file does not exist at #{ini_path}" if offline
53
54
  uri = URI(cached_versions[version] + ini_name)
54
55
  File.open(ini_path, 'wb') do |f|
@@ -66,6 +67,24 @@ module U3d
66
67
  result
67
68
  end
68
69
 
70
+ def create_linux_ini(version, size, url)
71
+ ini_name = format(INI_NAME, version: version, os: 'linux')
72
+ Utils.ensure_dir(default_ini_path)
73
+ ini_path = File.expand_path(ini_name, default_ini_path)
74
+ return if File.file? ini_path
75
+ File.open(ini_path, 'wb') do |f|
76
+ f.write %([Unity]
77
+ ; -- NOTE --
78
+ ; This is not an official Unity file
79
+ ; This has been created by u3d
80
+ ; ----------
81
+ title=Unity
82
+ size=#{size}
83
+ url=#{url}
84
+ )
85
+ end
86
+ end
87
+
69
88
  private
70
89
 
71
90
  def default_ini_path