rbbt-util 5.28.6 → 5.28.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rbbt/entity.rb +1 -1
  3. data/lib/rbbt/fix_width_table.rb +5 -4
  4. data/lib/rbbt/persist.rb +9 -3
  5. data/lib/rbbt/persist/tsv/adapter.rb +0 -1
  6. data/lib/rbbt/persist/tsv/fix_width_table.rb +5 -3
  7. data/lib/rbbt/resource.rb +12 -6
  8. data/lib/rbbt/tsv/accessor.rb +10 -2
  9. data/lib/rbbt/tsv/dumper.rb +14 -2
  10. data/lib/rbbt/tsv/parallel/traverse.rb +2 -0
  11. data/lib/rbbt/tsv/util.rb +5 -1
  12. data/lib/rbbt/util/R.rb +2 -2
  13. data/lib/rbbt/util/cmd.rb +10 -0
  14. data/lib/rbbt/util/config.rb +2 -1
  15. data/lib/rbbt/util/misc/bgzf.rb +1 -1
  16. data/lib/rbbt/util/misc/inspect.rb +11 -7
  17. data/lib/rbbt/util/misc/system.rb +1 -1
  18. data/lib/rbbt/util/named_array.rb +1 -1
  19. data/lib/rbbt/util/open.rb +18 -17
  20. data/lib/rbbt/workflow/accessor.rb +1 -1
  21. data/lib/rbbt/workflow/definition.rb +8 -4
  22. data/lib/rbbt/workflow/integration/ansible.rb +53 -0
  23. data/lib/rbbt/workflow/integration/ansible/workflow.rb +60 -0
  24. data/lib/rbbt/workflow/step.rb +22 -5
  25. data/lib/rbbt/workflow/step/accessor.rb +6 -6
  26. data/lib/rbbt/workflow/util/archive.rb +3 -0
  27. data/lib/rbbt/workflow/util/orchestrator.rb +228 -0
  28. data/lib/rbbt/workflow/util/provenance.rb +7 -3
  29. data/lib/rbbt/workflow/util/trace.rb +182 -0
  30. data/share/rbbt_commands/ansible +55 -0
  31. data/share/rbbt_commands/purge_job +2 -5
  32. data/share/rbbt_commands/system/status +23 -23
  33. data/share/rbbt_commands/workflow/forget_deps +10 -3
  34. data/share/rbbt_commands/workflow/prov +2 -1
  35. data/test/rbbt/association/test_index.rb +6 -6
  36. data/test/rbbt/knowledge_base/test_query.rb +3 -3
  37. data/test/rbbt/knowledge_base/test_registry.rb +1 -1
  38. data/test/rbbt/persist/tsv/test_cdb.rb +0 -7
  39. data/test/rbbt/persist/tsv/test_kyotocabinet.rb +2 -8
  40. data/test/rbbt/persist/tsv/test_leveldb.rb +0 -6
  41. data/test/rbbt/persist/tsv/test_lmdb.rb +0 -6
  42. data/test/rbbt/persist/tsv/test_tokyocabinet.rb +15 -14
  43. data/test/rbbt/test_entity.rb +0 -1
  44. data/test/rbbt/test_knowledge_base.rb +3 -4
  45. data/test/rbbt/test_persist.rb +10 -6
  46. data/test/rbbt/test_workflow.rb +17 -16
  47. data/test/rbbt/tsv/parallel/test_traverse.rb +14 -0
  48. data/test/rbbt/tsv/test_accessor.rb +11 -0
  49. data/test/rbbt/tsv/test_attach.rb +0 -2
  50. data/test/rbbt/tsv/test_index.rb +6 -7
  51. data/test/rbbt/tsv/test_manipulate.rb +22 -3
  52. data/test/rbbt/util/R/test_model.rb +2 -1
  53. data/test/rbbt/util/R/test_plot.rb +0 -2
  54. data/test/rbbt/util/concurrency/test_processes.rb +1 -1
  55. data/test/rbbt/util/misc/test_bgzf.rb +11 -7
  56. data/test/rbbt/util/misc/test_lock.rb +0 -1
  57. data/test/rbbt/util/misc/test_multipart_payload.rb +1 -1
  58. data/test/rbbt/util/misc/test_pipes.rb +0 -5
  59. data/test/rbbt/util/test_R.rb +1 -0
  60. data/test/rbbt/util/test_log.rb +4 -6
  61. data/test/rbbt/util/test_misc.rb +0 -2
  62. data/test/rbbt/util/test_open.rb +0 -1
  63. data/test/rbbt/util/test_python.rb +17 -1
  64. data/test/rbbt/workflow/test_remote_workflow.rb +1 -1
  65. data/test/rbbt/workflow/test_schedule.rb +0 -0
  66. data/test/rbbt/workflow/test_step.rb +8 -3
  67. data/test/rbbt/workflow/util/test_orchestrator.rb +273 -0
  68. metadata +11 -5
  69. data/lib/rbbt/workflow/schedule.rb +0 -238
  70. data/test/rbbt/workflow/remote/test_client.rb +0 -56
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 900106fb1799c857a482187c7cb7d18a40c35dbeb5dd231bf8e1851235e28c63
4
- data.tar.gz: e5ec2271b3ada0b282963f4ffc7e2b623d8bb84fcb72867a6848b93a80052463
3
+ metadata.gz: 6ae5e7a2e944e3aabababe528af35f6d0b9002db33c661cf75dd3f6f49bd1b94
4
+ data.tar.gz: 94a6ec3c4a32e3632d59872c17b449a5c6be624cf999b2bb3469647933a0ba28
5
5
  SHA512:
6
- metadata.gz: 1a418d5b4fe4369f7c25dd063c60a65c2b6c0bd7e3dbf9fd156fe875f85ef1b2eeb4d2eb1a03472e191de98bbfbae20abdb86877d381193f86fe4c8325d2f391
7
- data.tar.gz: 5225d6dbdbab08d0ed5239eba91bbe3d2a8dd16c92c24fe177b66ed3b9d4baef4e24818057de8bf694edf715aabd72b481926a2466c76492c27b75be25634b27
6
+ metadata.gz: 433ee5aa750efe1f609d6ee37a30b4438fc512719de2a7062d8696e2e1f15dec5caa33e2e63dce2ea7082d9f741fadf3314991a6a5e4a30d770f9fe6c7a04207
7
+ data.tar.gz: d16905de756df32abacd314b8793d4792c9852d76267be7ef71b6c5e0ae4e08f8b9ce04f34f2ceba3cd36ed8cda18966e737a893439b2451a9cde43a334afcb6
@@ -38,7 +38,7 @@ module Entity
38
38
  if value.to_s == k.to_s
39
39
  found = k
40
40
  break
41
- elsif value =~ /\(#{Regexp.quote k}\)/
41
+ elsif value.to_s =~ /\(#{Regexp.quote k}\)/
42
42
  found = k
43
43
  break
44
44
  end
@@ -67,7 +67,7 @@ class FixWidthTable
67
67
 
68
68
  def format(pos, value)
69
69
  padding = value_size - value.length
70
- if range
70
+ if @range
71
71
  (pos + [padding, value + ("\0" * padding)]).pack("llll#{mask}")
72
72
  else
73
73
  [pos, padding, value + ("\0" * padding)].pack("ll#{mask}")
@@ -105,7 +105,7 @@ class FixWidthTable
105
105
 
106
106
  def idx_value(index)
107
107
  return nil if index < 0 or index >= size
108
- @file.seek((range ? 17 : 9 ) + (record_size) * index, IO::SEEK_SET)
108
+ @file.seek((@range ? 17 : 9 ) + (record_size) * index, IO::SEEK_SET)
109
109
  padding = @file.read(4).unpack("l").first+1
110
110
  txt = @file.read(value_size)
111
111
  str = txt.unpack(mask).first
@@ -277,7 +277,8 @@ class FixWidthTable
277
277
 
278
278
  def [](pos)
279
279
  return [] if size == 0
280
- if range
280
+ self.read
281
+ if @range
281
282
  get_range(pos)
282
283
  else
283
284
  get_point(pos)
@@ -286,7 +287,7 @@ class FixWidthTable
286
287
 
287
288
  def overlaps(pos, value = false)
288
289
  return [] if size == 0
289
- idxs = if range
290
+ idxs = if @range
290
291
  get_range(pos, true)
291
292
  else
292
293
  get_point(pos, true)
@@ -25,12 +25,18 @@ module Persist
25
25
  MEMORY = {} unless defined? MEMORY
26
26
  MAX_FILE_LENGTH = 150
27
27
 
28
- def self.newer?(path, file)
28
+ # Is 'file' newer than 'path'? return non-true if path is newer than file
29
+ def self.newer?(path, file, by_link = false)
29
30
  return true if not Open.exists?(file)
30
31
  path = path.find if Path === path
31
32
  file = file.find if Path === file
32
- patht = Open.mtime(path)
33
- filet = Open.mtime(file)
33
+ if by_link
34
+ patht = File.lstat(path).mtime
35
+ filet = File.lstat(file).mtime
36
+ else
37
+ patht = Open.mtime(path)
38
+ filet = Open.mtime(file)
39
+ end
34
40
  return true if patht.nil? || filet.nil?
35
41
  diff = patht - filet
36
42
  return diff if diff < 0
@@ -52,7 +52,6 @@ module Persist
52
52
  def lock
53
53
  return yield if @locked
54
54
  lock_filename = Persist.persistence_path(persistence_path, {:dir => TSV.lock_dir})
55
- Log.stack caller if $LOG
56
55
  Misc.lock(lock_filename) do
57
56
  begin
58
57
  @locked = true
@@ -52,7 +52,7 @@ module Persist
52
52
  if TSV::ENTRY_KEYS.include? key
53
53
  set_metadata(key, value)
54
54
  else
55
- if range
55
+ if @range
56
56
  add_range_point key, value
57
57
  else
58
58
  add key, value
@@ -61,7 +61,7 @@ module Persist
61
61
  end
62
62
 
63
63
  def add(key, value)
64
- key = pos_function.call(key) if pos_function and not (range and Array === key)
64
+ key = pos_function.call(key) if pos_function and not (@range and Array === key)
65
65
  super(key, value)
66
66
  end
67
67
 
@@ -85,8 +85,10 @@ module Persist
85
85
  end
86
86
 
87
87
  def each
88
+ read
88
89
  @size.times do |i|
89
- yield i, value(i)
90
+ v = idx_value(i)
91
+ yield i, v
90
92
  end
91
93
  end
92
94
 
@@ -112,13 +112,19 @@ module Resource
112
112
  end
113
113
  when Net::HTTPRedirection, Net::HTTPFound
114
114
  location = response['location']
115
- Log.debug("Feching directory from: #{location}. Into: #{final_path}")
116
- FileUtils.mkdir_p final_path unless File.exist? final_path
117
- TmpFile.with_file do |tmp_dir|
118
- Misc.in_dir tmp_dir do
119
- CMD.cmd('tar xvfz -', :in => Open.open(location, :nocache => true))
115
+ if location.include? 'get_directory'
116
+ Log.debug("Feching directory from: #{location}. Into: #{final_path}")
117
+ FileUtils.mkdir_p final_path unless File.exist? final_path
118
+ TmpFile.with_file do |tmp_dir|
119
+ Misc.in_dir tmp_dir do
120
+ CMD.cmd('tar xvfz -', :in => Open.open(location, :nocache => true))
121
+ end
122
+ FileUtils.mv tmp_dir, final_path
123
+ end
124
+ else
125
+ Open.open(location, :nocache => true) do |s|
126
+ Misc.sensiblewrite(final_path, s)
120
127
  end
121
- FileUtils.mv tmp_dir, final_path
122
128
  end
123
129
  when Net::HTTPInternalServerError
124
130
  @server_missing_resource_cache << url
@@ -550,9 +550,17 @@ module TSV
550
550
  "\t" << ([""] * fields.length) * "\t" << "\n"
551
551
  end
552
552
  when Array
553
- "\t" << values.collect{|v| Array === v ? v * "|" : v} * "\t" << "\n"
553
+ if fields.nil? or fields.empty?
554
+ "\n"
555
+ else
556
+ "\t" << values.collect{|v| Array === v ? v * "|" : v} * "\t" << "\n"
557
+ end
554
558
  else
555
- "\t" << values.to_s << "\n"
559
+ if fields.nil? or fields.empty?
560
+ "\n"
561
+ else
562
+ "\t" << values.to_s << "\n"
563
+ end
556
564
  end
557
565
  end
558
566
 
@@ -32,9 +32,21 @@ module TSV
32
32
  sep + ([""] * fields.length) * sep << "\n"
33
33
  end
34
34
  when Array
35
- sep + (values.collect{|v| Array === v ? v * "|" : v} * sep) << "\n"
35
+ if fields.nil?
36
+ sep + (values.collect{|v| Array === v ? v * "|" : v} * sep) << "\n"
37
+ elsif fields.empty?
38
+ "\n"
39
+ else
40
+ sep + (values.collect{|v| Array === v ? v * "|" : v} * sep) << "\n"
41
+ end
36
42
  else
37
- sep + values.to_s << "\n"
43
+ if fields.nil?
44
+ sep + values.to_s + "\n"
45
+ elsif fields.empty?
46
+ "\n"
47
+ else
48
+ sep + values.to_s << "\n"
49
+ end
38
50
  end
39
51
  end
40
52
 
@@ -624,6 +624,8 @@ module TSV
624
624
  def self.traverse(obj, options = {}, &block)
625
625
  into = options[:into]
626
626
 
627
+ into = options[:into] = Open.open(into, :mode => "w") if Misc.is_filename?(into)
628
+
627
629
  case into
628
630
  when :stream
629
631
  sout = Misc.open_pipe false, false do |sin|
@@ -184,7 +184,11 @@ module TSV
184
184
  str = ""
185
185
  str << preamble.strip << "\n" if preamble and not preamble.empty?
186
186
  if fields
187
- str << header_hash << (key_field || "ID").to_s << sep << (fields * sep) << "\n"
187
+ if fields.empty?
188
+ str << header_hash << (key_field || "ID").to_s << "\n"
189
+ else
190
+ str << header_hash << (key_field || "ID").to_s << sep << (fields * sep) << "\n"
191
+ end
188
192
  end
189
193
 
190
194
  str
@@ -41,7 +41,7 @@ source('#{UTIL}');
41
41
 
42
42
  if monitor
43
43
  #io = CMD.cmd('R --no-save --quiet', options.merge(:in => cmd, :pipe => true, :log => true))
44
- io = CMD.cmd('R --no-save --quiet', options.merge(:in => cmd, :pipe => true, :log => true))
44
+ io = CMD.cmd('R --no-save --quiet', options.merge(:in => cmd, :pipe => true, :log => true, :xvfb => true))
45
45
  while line = io.gets
46
46
  case monitor
47
47
  when Proc
@@ -52,7 +52,7 @@ source('#{UTIL}');
52
52
  end
53
53
  nil
54
54
  else
55
- CMD.cmd('R --no-save --slave --quiet', options.merge(:in => cmd))
55
+ CMD.cmd('R --no-save --slave --quiet', options.merge(:in => cmd, :xvfb => true))
56
56
  end
57
57
  end
58
58
 
@@ -98,7 +98,9 @@ module CMD
98
98
  post = options.delete(:post)
99
99
  log = options.delete(:log)
100
100
  no_fail = options.delete(:no_fail)
101
+ no_fail = options.delete(:nofail) if no_fail.nil?
101
102
  no_wait = options.delete(:no_wait)
103
+ xvfb = options.delete(:xvfb)
102
104
 
103
105
  dont_close_in = options.delete(:dont_close_in)
104
106
 
@@ -116,6 +118,14 @@ module CMD
116
118
 
117
119
  end
118
120
 
121
+ case xvfb
122
+ when TrueClass
123
+ cmd = "xvfb-run --server-args='-screen 0 1024x768x24' --auto-servernum #{cmd}"
124
+ when String
125
+ cmd = "xvfb-run --server-args='#{xvfb}' --auto-servernum --server-num=1 #{cmd}"
126
+ when String
127
+ end
128
+
119
129
  if stderr == true
120
130
  stderr = Log::HIGH
121
131
  end
@@ -15,7 +15,7 @@ module Rbbt::Config
15
15
  end
16
16
 
17
17
  def self.load_file(file)
18
- Log.debug "Loading file: #{ file }"
18
+ Log.debug "Loading config file: #{ file }"
19
19
  TSV.traverse file, :type => :array do |line|
20
20
  next if line =~ /^#/
21
21
  key, value, *tokens = line.strip.split(/\s/)
@@ -85,6 +85,7 @@ module Rbbt::Config
85
85
  priorities
86
86
  end
87
87
 
88
+ # For equal priorities the matching prioritizes tokens ealier in the list
88
89
  def self.get(key, *tokens)
89
90
  options = tokens.pop if Hash === tokens.last
90
91
  default = options.nil? ? nil : options[:default]
@@ -7,7 +7,7 @@ module Bgzf
7
7
 
8
8
  def self.bgzip_cmd
9
9
  @@bgzip_cmd ||= begin
10
- path = `bash -c "type -p bgzips"`.strip
10
+ path = `bash -c "type -p bgzip"`.strip
11
11
  if path.empty?
12
12
  Rbbt.claim Rbbt.software.opt.htslib, :install, Rbbt.share.install.software.HTSLIB.find(:lib)
13
13
  Rbbt.software.opt.htslib.produce
@@ -287,15 +287,19 @@ module Misc
287
287
  when Symbol
288
288
  obj.to_s
289
289
  when (defined?(Path) and Path)
290
- if obj.exists?
291
- if obj.directory?
292
- files = obj.glob("**/*")
293
- "directory: #{Misc.fingerprint(files)}"
290
+ if Step === obj.resource
291
+ "Step file: " + obj
292
+ else
293
+ if obj.exists?
294
+ if obj.directory?
295
+ files = obj.glob("**/*")
296
+ "directory: #{Misc.fingerprint(files)}"
297
+ else
298
+ "file: " << Open.realpath(obj) << "--" << mtime_str(obj)
299
+ end
294
300
  else
295
- "file: " << Open.realpath(obj) << "--" << mtime_str(obj)
301
+ obj + " (file missing)"
296
302
  end
297
- else
298
- obj + " (file missing)"
299
303
  end
300
304
  when String
301
305
  if Misc.is_filename?(obj) and ! %w(. ..).include?(obj)
@@ -113,7 +113,7 @@ end
113
113
  end
114
114
 
115
115
  def self.is_filename?(string)
116
- return true if defined? PATH and Path === string
116
+ return true if defined? Path and Path === string
117
117
  return true if string.respond_to? :exists
118
118
  return true if String === string and string.length < 265 and File.exist?(string)
119
119
  return false
@@ -117,7 +117,7 @@ module NamedArray
117
117
  #end
118
118
 
119
119
  def each(&block)
120
- if defined?(Entity) and not @fields.nil? and not @fields.empty?
120
+ if defined?(Entity) && ! (@fields.nil? || @fields.empty?)
121
121
  i = 0
122
122
  super do |elem|
123
123
  field = @fields[i]
@@ -16,9 +16,9 @@ module Open
16
16
  GREP_CMD = begin
17
17
  if ENV["GREP_CMD"]
18
18
  ENV["GREP_CMD"]
19
- elsif File.exists?('/bin/grep')
19
+ elsif File.exist?('/bin/grep')
20
20
  "/bin/grep"
21
- elsif File.exists?('/usr/bin/grep')
21
+ elsif File.exist?('/usr/bin/grep')
22
22
  "/usr/bin/grep"
23
23
  else
24
24
  "grep"
@@ -262,7 +262,7 @@ module Open
262
262
  if (dir_sub_path = find_repo_dir(file))
263
263
  remove_from_repo(*dir_sub_path)
264
264
  else
265
- FileUtils.rm(file) if File.exists?(file) or Open.broken_link?(file)
265
+ FileUtils.rm(file) if File.exist?(file) or Open.broken_link?(file)
266
266
  end
267
267
  end
268
268
 
@@ -333,7 +333,7 @@ module Open
333
333
  nil
334
334
  else
335
335
  target = target.find if Path === target
336
- if ! File.exists?(target)
336
+ if ! File.exist?(target)
337
337
  FileUtils.mkdir_p target
338
338
  end
339
339
  end
@@ -344,8 +344,8 @@ module Open
344
344
  target = target.find if Path === target
345
345
 
346
346
  target = File.join(target, File.basename(source)) if File.directory? target
347
- FileUtils.mkdir_p File.dirname(target) unless File.exists?(File.dirname(target))
348
- FileUtils.rm target if File.exists?(target)
347
+ FileUtils.mkdir_p File.dirname(target) unless File.exist?(File.dirname(target))
348
+ FileUtils.rm target if File.exist?(target)
349
349
  FileUtils.ln_s source, target
350
350
  end
351
351
 
@@ -353,8 +353,8 @@ module Open
353
353
  source = source.find if Path === source
354
354
  target = target.find if Path === target
355
355
 
356
- FileUtils.mkdir_p File.dirname(target) unless File.exists?(File.dirname(target))
357
- FileUtils.rm target if File.exists?(target)
356
+ FileUtils.mkdir_p File.dirname(target) unless File.exist?(File.dirname(target))
357
+ FileUtils.rm target if File.exist?(target)
358
358
  FileUtils.ln source, target
359
359
  end
360
360
 
@@ -362,8 +362,8 @@ module Open
362
362
  source = source.find if Path === source
363
363
  target = target.find if Path === target
364
364
 
365
- FileUtils.mkdir_p File.dirname(target) unless File.exists?(File.dirname(target))
366
- FileUtils.rm target if File.exists?(target)
365
+ FileUtils.mkdir_p File.dirname(target) unless File.exist?(File.dirname(target))
366
+ FileUtils.rm target if File.exist?(target)
367
367
  begin
368
368
  CMD.cmd("ln -L '#{ source }' '#{ target }'")
369
369
  rescue ProcessFailed
@@ -397,7 +397,7 @@ module Open
397
397
  if dir_sub_path_source.nil? and dir_sub_path_target.nil?
398
398
  FileUtils.mkdir_p File.dirname(target) unless File.exist? File.dirname(target)
399
399
  tmp_target = File.join(File.dirname(target), '.tmp_mv.' + File.basename(target))
400
- FileUtils.cp source, tmp_target
400
+ FileUtils.cp_r source, tmp_target
401
401
  FileUtils.mv tmp_target, target
402
402
  return
403
403
  end
@@ -475,6 +475,7 @@ module Open
475
475
  File.exist?(file) #|| File.symlink?(file)
476
476
  end
477
477
  end
478
+
478
479
  class << self
479
480
  alias exist? exists?
480
481
  end
@@ -747,7 +748,7 @@ module Open
747
748
  if (dir_sub_path = find_repo_dir(path))
748
749
  writable_repo?(*dir_sub_path)
749
750
  else
750
- if File.exists?(path)
751
+ if File.exist?(path)
751
752
  File.writable?(path)
752
753
  else
753
754
  File.writable?(File.dirname(File.expand_path(path)))
@@ -776,14 +777,14 @@ module Open
776
777
  file = file.find if Path === file
777
778
  begin
778
779
  if File.symlink?(file) || File.stat(file).nlink > 1
779
- if File.exists?(file + '.info') && defined?(Step)
780
+ if File.exist?(file + '.info') && defined?(Step)
780
781
  done = Step::INFO_SERIALIZER.load(Open.open(file + '.info'))[:done]
781
782
  return done if done
782
783
  end
783
784
 
784
785
  file = Pathname.new(file).realpath.to_s
785
786
  end
786
- return nil unless File.exists?(file)
787
+ return nil unless File.exist?(file)
787
788
  File.mtime(file)
788
789
  rescue
789
790
  nil
@@ -793,7 +794,7 @@ module Open
793
794
 
794
795
  def self.update_mtime(path, target)
795
796
  if File.symlink?(target) || File.stat(target).nlink > 1
796
- if File.exists?(target + '.info')
797
+ if File.exist?(target + '.info')
797
798
  target = target + '.info'
798
799
  else
799
800
  target = Pathname.new(target).realpath.to_s
@@ -801,7 +802,7 @@ module Open
801
802
  end
802
803
 
803
804
  CMD.cmd("touch -r '#{path}' '#{target}'")
804
- CMD.cmd("touch -r '#{path}.info' '#{target}'") if File.exists?(path + '.info')
805
+ CMD.cmd("touch -r '#{path}.info' '#{target}'") if File.exist?(path + '.info')
805
806
  end
806
807
 
807
808
  def self.atime(file)
@@ -823,7 +824,7 @@ module Open
823
824
  end
824
825
 
825
826
  def self.broken_link?(path)
826
- File.symlink?(path) && ! File.exists?(File.readlink(path))
827
+ File.symlink?(path) && ! File.exist?(File.readlink(path))
827
828
  end
828
829
 
829
830
  def self.download(url, path)