Class: CloudRCS::Binary
- Inherits:
-
PrimitivePatch
- Object
- ActiveRecord::Base
- PrimitivePatch
- CloudRCS::Binary
- Defined in:
- lib/cloud_rcs/patch_types/binary.rb
Constant Summary
Constants inherited from PrimitivePatch
Class Method Summary collapse
- .binary_to_hex(bin) ⇒ Object
- .generate(orig_file, changed_file) ⇒ Object
-
.hex_to_binary(hex) ⇒ Object
We want to store the contents of a binary file encoded as a hexidecimal value.
- .parse(contents) ⇒ Object
-
.priority ⇒ Object
Use a low priority so that the binary patch generating method will be called before the hunk patch generating method.
Instance Method Summary collapse
- #added ⇒ Object
- #apply_to(file) ⇒ Object
- #commute(patch) ⇒ Object
- #inverse ⇒ Object
- #lengthnew ⇒ Object
- #lengthold ⇒ Object
- #removed ⇒ Object
- #to_s ⇒ Object
Methods inherited from PrimitivePatch
#apply!, escape_path, merge, #named_patch?, #new_path, #primitive_patch?, #to_a, unescape_path
Class Method Details
.binary_to_hex(bin) ⇒ Object
222 223 224 |
# File 'lib/cloud_rcs/patch_types/binary.rb', line 222 def binary_to_hex(bin) bin.unpack("H*").first end |
.generate(orig_file, changed_file) ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/cloud_rcs/patch_types/binary.rb', line 135 def generate(orig_file, changed_file) return if orig_file.nil? and changed_file.nil? return unless (orig_file and orig_file.contents.is_binary_data?) or (changed_file and changed_file.contents.is_binary_data?) # Convert binary data to hexadecimal for storage in a text # file orig_hex = orig_file ? binary_to_hex(orig_file.contents).scan(/.{2}/) : [] changed_hex = changed_file ? binary_to_hex(changed_file.contents).scan(/.{2}/) : [] file_path = orig_file ? orig_file.path : changed_file.path diffs = Diff::LCS.diff(orig_hex, changed_hex) chunks = [] offset = 0 diffs.each do |d| # We need to recalculate positions for removals - just as in # hunk generation. unless chunks.empty? offset += chunks.last.lengthnew - chunks.last.lengthold end d.collect! do |l| if l.action == '-' Diff::LCS::Change.new(l.action, l.position + (offset / 2), l.element) else l end end position = d.first.position * 2 removed = d.find_all { |l| l.action == '-' }.collect { |l| l.element }.join added = d.find_all { |l| l.action == '+' }.collect { |l| l.element }.join unless removed.blank? and added.blank? chunks << Binary.new(:contents => [removed, added], :position => position, :path => file_path) end end return chunks end |
.hex_to_binary(hex) ⇒ Object
We want to store the contents of a binary file encoded as a hexidecimal value. These two methods allow for translating between binary and hexidecimal.
Code borrowed from: 4thmouse.com/index.php/2008/02/18/converting-hex-to-binary-in-4-languages/
218 219 220 |
# File 'lib/cloud_rcs/patch_types/binary.rb', line 218 def hex_to_binary(hex) hex.to_a.pack("H*") end |
.parse(contents) ⇒ Object
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/cloud_rcs/patch_types/binary.rb', line 181 def parse(contents) unless contents =~ /^binary\s+(\S+)\s+(\d+)\s+(.*)$/m raise ParseException.new(true), "Failed to parse binary patch: \"#{contents}\"" end file_path = unescape_path($1) starting_position = $2.to_i contents = $3 removed, added = [], [] removed_offset = 0 added_offset = 0 contents.split("\n").each do |line| if line =~ /^-([\S]*)\s*$/ removed << $1 removed_offset += 1 elsif line =~ /^\+([\S]*)\s*$/ added << $1 added_offset += 1 else raise "Failed to parse a line in binary patch: \"#{line}\"" end end removed = removed.join added = added.join return Binary.new(:path => file_path, :position => starting_position, :contents => [removed, added]) end |
.priority ⇒ Object
Use a low priority so that the binary patch generating method will be called before the hunk patch generating method
131 132 133 |
# File 'lib/cloud_rcs/patch_types/binary.rb', line 131 def priority 20 end |
Instance Method Details
#added ⇒ Object
115 116 117 |
# File 'lib/cloud_rcs/patch_types/binary.rb', line 115 def added contents.last end |
#apply_to(file) ⇒ Object
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/cloud_rcs/patch_types/binary.rb', line 9 def apply_to(file) return file unless file.path == path hex_contents = Binary.binary_to_hex(file.contents) # Check that the patch matches the file contents unless hex_contents[position...position+lengthold] == removed raise ApplyException.new(true), "Portion of binary patch marked for removal does not match existing contents in file. Existing contents at position #{position}: '#{hex_contents[position...position+lengthold]}' ; marked for removal: '#{removed}'" end # Then, remove stuff unless removed.blank? hex_contents[position...position+lengthold] = "" end # Finally, add stuff unless added.blank? hex_contents.insert(position, added) end file.contents = Binary.hex_to_binary(hex_contents) return file end |
#commute(patch) ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/cloud_rcs/patch_types/binary.rb', line 40 def commute(patch) if patch.is_a? Binary and patch.path == self.path # self is applied first and precedes patch in the file if self.position + self.lengthnew < patch.position patch1 = Binary.new(:path => patch.path, :position => (patch.position - self.lengthnew + self.lengthold), :contents => patch.contents) patch2 = Binary.new(:path => self.path, :position => self.position, :contents => self.contents) # self is applied first, but is preceded by patch in the file elsif patch.position + patch.lengthold < self.position patch1 = Binary.new(:path => patch.path, :position => patch.position, :contents => patch.contents) patch2 = Binary.new(:path => self.path, :position => (self.position + patch.lengthnew - patch.lengthold), :contents => self.contents) # patch precedes self in file, but bumps up against it elsif patch.position + patch.lengthnew == self.position and self.lengthold != 0 and patch.lengthold != 0 and self.lengthnew != 0 and patch.lengthnew != 0 patch1 = Binary.new(:path => patch.path, :position => patch.position, :contents => patch.contents) patch2 = Binary.new(:path => self.path, :position => (self.position - patch.lengthnew + patch.lengthold), :contents => self.contents) # self precedes patch in file, but bumps up against it elsif self.position + self.lengthold == patch.position and self.lengthold != 0 and patch.lengthold != 0 and self.lengthnew != 0 and patch.lengthnew != 0 patch1 = Binary.new(:path => patch.path, :position => patch.position, :contents => patch.contents) patch2 = Binary.new(:path => self.path, :position => (self.position + patch.lengthnew - patch.lengthold), :contents => self.contents) # Patches overlap. This is a conflict scenario else raise CommuteException.new(true), "Conflict: binary patches overlap." end elsif patch.is_a? Rmfile and patch.path == self.path raise CommuteException.new(true), "Conflict: cannot modify a file after it is removed." elsif patch.is_a? Move and self.path == patch.original_path patch1 = patch.clone patch2 = self.clone patch2.path = patch.new_path # Commutation is trivial else patch1, patch2 = patch, self end return patch1, patch2 end |
#inverse ⇒ Object
33 34 35 36 37 38 |
# File 'lib/cloud_rcs/patch_types/binary.rb', line 33 def inverse Binary.new(:path => path, :position => position, :contents => [added, removed], :inverted => true) end |
#lengthnew ⇒ Object
123 124 125 |
# File 'lib/cloud_rcs/patch_types/binary.rb', line 123 def lengthnew added.length end |
#lengthold ⇒ Object
119 120 121 |
# File 'lib/cloud_rcs/patch_types/binary.rb', line 119 def lengthold removed.length end |
#removed ⇒ Object
111 112 113 |
# File 'lib/cloud_rcs/patch_types/binary.rb', line 111 def removed contents.first end |
#to_s ⇒ Object
104 105 106 107 108 109 |
# File 'lib/cloud_rcs/patch_types/binary.rb', line 104 def to_s header = "binary #{self.class.escape_path(path)} #{position}" old = removed.scan(/.{1,78}/).collect { |c| '-' + c }.join("\n") new = added.scan(/.{1,78}/).collect { |c| '+' + c }.join("\n") return [header, old, new].delete_if { |e| e.blank? }.join("\n") end |