Project

General

Profile

Feature #1410 » buffered_io.patch

Pierre Paysant-Le Roux, 2008-11-24 23:27

View differences:

test/unit/lib/buffered_io_test.rb (revision 0)
1
# This program is free software; you can redistribute it and/or
2
# modify it under the terms of the GNU General Public License
3
# as published by the Free Software Foundation; either version 2
4
# of the License, or (at your option) any later version.
5
# 
6
# This program is distributed in the hope that it will be useful,
7
# but WITHOUT ANY WARRANTY; without even the implied warranty of
8
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9
# GNU General Public License for more details.
10
# 
11
# You should have received a copy of the GNU General Public License
12
# along with this program; if not, write to the Free Software
13
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
14

  
15
require File.dirname(__FILE__) + '/../../test_helper'
16

  
17
class BufferedIOTest < Test::Unit::TestCase
18

  
19
  CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
20
  def self.rand_string(length=8)
21
    s=''
22
    length.times{ s << CHARS[rand(CHARS.length)] }
23
    s
24
  end
25

  
26
  TEST_STRING=rand_string(1000)
27
  
28
  def test_stop_caching
29
    io = BufferedIO.new(StringIO.new(TEST_STRING))
30
    io.cache = true
31
    t = io.read(5)
32
    assert_equal 5, io.internal_buffer.size
33
    io.cache = false
34
    t += io.read
35
    assert_equal TEST_STRING, t
36
    assert_equal 5, io.internal_buffer.size
37
  end
38

  
39
  def test_is_binary_data
40
    io = BufferedIO.new(StringIO.new(TEST_STRING))
41
    io2 = BufferedIO.new(StringIO.new(TEST_STRING))
42
    io2.cache = true
43
    [io, io2].each do |io|
44
      assert_equal false, io.is_binary_data?
45
      t = io.read
46
      assert_equal TEST_STRING, t
47
    end
48
  end
49

  
50
end
app/models/repository.rb (working copy)
67 67
    scm.properties(path, identifier)
68 68
  end
69 69
  
70
  def cat(path, identifier=nil)
71
    scm.cat(path, identifier)
70
  def cat(path, identifier=nil, &block)
71
    scm.cat(path, identifier, &block)
72 72
  end
73 73
  
74 74
  def diff(path, rev, rev_to)
app/controllers/repositories_controller.rb (working copy)
110 110
  def entry
111 111
    @entry = @repository.entry(@path, @rev)
112 112
    show_error_not_found and return unless @entry
113
    
113

  
114 114
    # If the entry is a dir, show the browser
115 115
    browse and return if @entry.is_dir?
116
    
117
    @content = @repository.cat(@path, @rev)
118
    show_error_not_found and return unless @content
119
    if 'raw' == params[:format] || @content.is_binary_data?
120
      # Force the download if it's a binary file
121
      send_data @content, :filename => @path.split('/').last
122
    else
123
      # Prevent empty lines when displaying a file with Windows style eol
124
      @content.gsub!("\r\n", "\n")
125
   end
116

  
117
    @repository.cat(@path, @rev) do |content|
118
      show_error_not_found and return unless content
119
      if 'raw' == params[:format] || content.is_binary_data?
120
        # Force the download if it's a binary file
121
        content.size = @entry.size
122
        send_data content, :filename => @path.split('/').last
123
      else
124
        # Prevent empty lines when displaying a file with Windows style eol
125
        @content = content.read.gsub("\r\n", "\n")
126
      end
127
    end
126 128
  end
127 129
  
128 130
  def annotate
lib/redmine/scm/adapters/subversion_adapter.rb (working copy)
190 190
          diff
191 191
        end
192 192
        
193
        def cat(path, identifier=nil)
193
        def cat(path, identifier=nil, &block)
194 194
          identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
195 195
          cmd = "#{SVN_BIN} cat #{target(URI.escape(path))}@#{identifier}"
196 196
          cmd << credentials_string
197 197
          cat = nil
198
          shellout(cmd) do |io|
198
          if block_given?
199
            shellout(cmd) do |io|
200
              io.binmode
201
              yield BufferedIO.new(io)
202
            end
203
          else
204
            io = shellout(cmd)
199 205
            io.binmode
200
            cat = io.read
206
            return BufferedIO.new(io)
201 207
          end
202
          return nil if $? && $?.exitstatus != 0
203
          cat
204 208
        end
205 209
        
206 210
        def annotate(path, identifier=nil)
lib/redmine/scm/adapters/bazaar_adapter.rb (working copy)
151 151
          diff
152 152
        end
153 153
        
154
        def cat(path, identifier=nil)
154
        def cat(path, identifier=nil, &block)
155 155
          cmd = "#{BZR_BIN} cat"
156 156
          cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
157 157
          cmd << " #{target(path)}"
158 158
          cat = nil
159
          shellout(cmd) do |io|
159
          if block_given?
160
            shellout(cmd) do |io|
161
              io.binmode
162
              yield BufferedIO.new(io)
163
            end
164
          else
165
            io = shellout(cmd)
160 166
            io.binmode
161
            cat = io.read
167
            return BufferedIO.new(io)
162 168
          end
163
          return nil if $? && $?.exitstatus != 0
164
          cat
165 169
        end
166 170
        
167 171
        def annotate(path, identifier=nil)
lib/redmine/scm/adapters/abstract_adapter.rb (working copy)
113 113
          return nil
114 114
        end
115 115
        
116
        def cat(path, identifier=nil)
116
        def cat(path, identifier=nil, &block)
117 117
          return nil
118 118
        end
119 119
        
......
172 172
        def self.shellout(cmd, &block)
173 173
          logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
174 174
          begin
175
            IO.popen(cmd, "r+") do |io|
175
            if block_given?
176
              IO.popen(cmd, "r+") do |io|
177
                io.close_write
178
                block.call(io)
179
              end
180
            else
181
              io = IO.popen(cmd, "r+")
176 182
              io.close_write
177
              block.call(io) if block_given?
183
              return io
178 184
            end
179 185
          rescue Errno::ENOENT => e
180 186
            msg = strip_credential(e.message)
lib/redmine/scm/adapters/git_adapter.rb (working copy)
249 249
          blame
250 250
        end
251 251
        
252
        def cat(path, identifier=nil)
252
        def cat(path, identifier=nil, &block)
253 253
          if identifier.nil?
254 254
            identifier = 'HEAD'
255 255
          end
256 256
          cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}"
257 257
          cat = nil
258
          shellout(cmd) do |io|
258
          if block_given?
259
            shellout(cmd) do |io|
260
              io.binmode
261
              yield BufferedIO.new(io)
262
            end
263
          else
264
            io = shellout(cmd)
259 265
            io.binmode
260
            cat = io.read
266
            return BufferedIO.new(io)
261 267
          end
262
          return nil if $? && $?.exitstatus != 0
263
          cat
264 268
        end
265 269
      end
266 270
    end
lib/redmine/scm/adapters/mercurial_adapter.rb (working copy)
169 169
          diff
170 170
        end
171 171
        
172
        def cat(path, identifier=nil)
172
        def cat(path, identifier=nil, &block)
173 173
          cmd = "#{HG_BIN} -R #{target('')} cat"
174 174
          cmd << " -r " + (identifier ? identifier.to_s : "tip")
175 175
          cmd << " #{target(path)}"
176 176
          cat = nil
177
          shellout(cmd) do |io|
177
          if block_given?
178
            shellout(cmd) do |io|
179
              io.binmode
180
              yield BufferedIO.new(io)
181
            end
182
          else
183
            io = shellout(cmd)
178 184
            io.binmode
179
            cat = io.read
185
            return BufferedIO.new(io)
180 186
          end
181
          return nil if $? && $?.exitstatus != 0
182
          cat
183 187
        end
184 188
        
185 189
        def annotate(path, identifier=nil)
lib/redmine/scm/adapters/filesystem_adapter.rb (working copy)
71 71
          entries.sort_by_name
72 72
        end
73 73
        
74
        def cat(path, identifier=nil)
75
          File.new(target(path), "rb").read
74
        def cat(path, identifier=nil, &block)
75
          if block_given?
76
            File.open(target(path), "rb") do |io|
77
              io.binmode
78
              yield BufferedIO.new(io)
79
            end
80
          else
81
            io = File.new(target(path), "rb")
82
            io.binmode
83
            return BufferedIO.new(io)
84
          end
76 85
        end
77 86

  
78 87
        private
lib/redmine/scm/adapters/cvs_adapter.rb (working copy)
241 241
          diff
242 242
        end  
243 243
        
244
        def cat(path, identifier=nil)
244
        def cat(path, identifier=nil, &block)
245 245
          identifier = (identifier) ? identifier : "HEAD"
246 246
          logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
247 247
          path_with_project="#{url}#{with_leading_slash(path)}"
......
249 249
          cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
250 250
          cmd << " -p #{shell_quote path_with_project}"
251 251
          cat = nil
252
          shellout(cmd) do |io|
253
            cat = io.read
252
          if block_given?
253
            shellout(cmd) do |io|
254
              io.binmode
255
              yield BufferedIO.new(io)
256
            end
257
          else
258
            io = shellout(cmd)
259
            io.binmode
260
            return BufferedIO.new(io)
254 261
          end
255
          return nil if $? && $?.exitstatus != 0
256
          cat
257 262
        end  
258 263

  
259 264
        def annotate(path, identifier=nil)
lib/redmine/scm/adapters/darcs_adapter.rb (working copy)
134 134
          diff
135 135
        end
136 136
        
137
        def cat(path, identifier=nil)
137
        def cat(path, identifier=nil, &block)
138 138
          cmd = "#{DARCS_BIN} show content --repodir #{@url}"
139 139
          cmd << " --match \"hash #{identifier}\"" if identifier
140 140
          cmd << " #{shell_quote path}"
141 141
          cat = nil
142
          shellout(cmd) do |io|
142
          if block_given?
143
            shellout(cmd) do |io|
144
              io.binmode
145
              yield BufferedIO.new(io)
146
            end
147
          else
148
            io = shellout(cmd)
143 149
            io.binmode
144
            cat = io.read
150
            return BufferedIO.new(io)
145 151
          end
146
          return nil if $? && $?.exitstatus != 0
147
          cat
148 152
        end
149 153

  
150 154
        private
lib/buffered_io.rb (revision 0)
1
# This program is free software; you can redistribute it and/or
2
# modify it under the terms of the GNU General Public License
3
# as published by the Free Software Foundation; either version 2
4
# of the License, or (at your option) any later version.
5
# 
6
# This program is distributed in the hope that it will be useful,
7
# but WITHOUT ANY WARRANTY; without even the implied warranty of
8
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9
# GNU General Public License for more details.
10
# 
11
# You should have received a copy of the GNU General Public License
12
# along with this program; if not, write to the Free Software
13
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
14
class BufferedIO
15

  
16
  attr_accessor :cache, :size
17

  
18
  def initialize(io)
19
    @source = io
20
    @cache = false
21
    @synced = true
22
  end
23

  
24
  def cache=(b)
25
    if b and synced?
26
      @cache = true
27
    else
28
      @cache = false
29
    end
30
  end
31

  
32
  def internal_buffer
33
    @internal_buffer || @internal_buffer = StringIO.new
34
  end
35

  
36
  def is_binary_data?
37
    if synced?
38
      position = internal_buffer.pos
39
      internal_buffer.rewind
40
      s = read_and_cache(4096)
41
      internal_buffer.pos = position
42
      s.is_binary_data?
43
    else
44
      raise Exception.new("Cannot do this test on an uncached buffer")      
45
    end
46
  end
47

  
48
  def synced?
49
    @synced
50
  end
51

  
52
  def read(x=nil)
53
    if cache
54
      read_and_cache(x)
55
    else
56
      read_and_dont_cache(x)
57
    end
58
  end 
59

  
60
  def pos=(x)
61
    if x > internal_buffer.size
62
      if sinced? and cache
63
        _append @source.read(x - internal_buffer.pos)
64
        internal_buffer.pos = x
65
      else
66
        @source.pos = x
67
      end
68
    else
69
      internal_buffer.pos = x
70
    end
71
  end 
72

  
73
  ["pos", "rewind", "tell"].each do |m| 
74
    define_method(m) do
75
      if synced?
76
        internal_buffer.send(m)
77
      else
78
        @source.send(m)
79
      end
80
    end 
81
  end
82
  
83
  private
84
  
85
  def _append(s) 
86
    unless s.nil?
87
      internal_buffer << s 
88
      internal_buffer.pos -= s.size
89
    end 
90
  end
91

  
92
  def read_source(x)
93
    @synced = false
94
    @source.read(x)
95
  end
96

  
97
  # Read the buffer by filling readed data in the cache.
98
  def read_and_cache(x=nil)
99
    if synced?
100
      to_read = x ? to_read = x + internal_buffer.pos - internal_buffer.size : nil 
101
      _append(@source.read(to_read)) if to_read.nil? or to_read > 0 
102
      internal_buffer.read(x)       
103
    else
104
      # Data is missing
105
      raise "Cannot cache a partially cached stream"
106
    end
107
  end
108

  
109
  # Read without filling the cache and ram by the way.
110
  # Not cached data will not be readable anymore if the
111
  # buffer cannot rewind.
112
  def read_and_dont_cache(x=nil)
113
    if synced?
114
      # Source and cache buffer are synced 
115
      if internal_buffer.pos == internal_buffer.size
116
        # There is nothing to read in the cache
117
        read_source(x)
118
      else # internal_buffer.pos < @source.pos
119
        if x.nil? or (internal_buffer.pos + x) > internal_buffer.size
120
          # There is a first part to read in the cache
121
          # and another in the source
122
          internal_buffer.read + read_source(x)
123
        else
124
          # All is in the cache
125
          internal_buffer.read(x)
126
        end
127
      end
128
    else
129
      @internal_buffer = nil
130
      read_source(x)
131
    end
132
  end
133
end
(1-1/2)