u3d 0.9.3 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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