Comments:<br><br><div class="gmail_quote">On Mon, Jul 14, 2008 at 4:05 PM, Michael Hanselmann &lt;public@hansmi.ch&gt; wrote:<br><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
- Reduced I/O load by only reading its content only once<br>
- Easier error handling (before a file could disappear between two<br>
 &nbsp;calls to open())<br>
---<br>
&nbsp;lib/Brackup/File.pm &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; 92 +++++++++++++++++++++-------------------<br>
&nbsp;lib/Brackup/PositionedChunk.pm | &nbsp; 47 ++-------------------<br>
&nbsp;2 files changed, 52 insertions(+), 87 deletions(-)<br>
<br>
diff --git a/lib/Brackup/File.pm b/lib/Brackup/File.pm<br>
index 827eff1..f24907c 100644<br>
--- a/lib/Brackup/File.pm<br>
+++ b/lib/Brackup/File.pm<br>
@@ -30,6 +30,8 @@ sub new {<br>
 &nbsp; &nbsp; die &quot;No path provided.&quot; unless $self-&gt;{path};<br>
 &nbsp; &nbsp; $self-&gt;{path} =~ s!^\./!!;<br>
<br>
+ &nbsp; &nbsp;$self-&gt;_read_file();</blockquote><div><br>I prefer style of no parens on nullary method call.<br>&nbsp;</div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<br>
+<br>
 &nbsp; &nbsp; return $self;<br>
&nbsp;}<br>
<br>
@@ -119,18 +121,22 @@ sub find_file_chunker {<br>
<br>
&nbsp;sub chunks {<br>
 &nbsp; &nbsp; my $self = shift;<br>
- &nbsp; &nbsp;# memoized:<br>
- &nbsp; &nbsp;return @{ $self-&gt;{chunks} } if $self-&gt;{chunks};<br>
+ &nbsp; &nbsp;return @{$self-&gt;{chunks}};<br>
+}<br>
+<br>
+sub _read_file {<br>
+ &nbsp; &nbsp;our ($self) = @_;</blockquote><div><br>Wrong use of our here.&nbsp; You want my.<br>&nbsp;</div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
&nbsp; &nbsp; # non-files don&#39;t have chunks<br>
- &nbsp; &nbsp;if (!$self-&gt;is_file) {<br>
+ &nbsp; &nbsp;unless ($self-&gt;is_file) {</blockquote><div><br>I love unless, but I prefer to use it only for exceptional cases, or when it just reads better.&nbsp; (the word &quot;unless&quot; itself reads like an exceptional case)&nbsp; I&#39;d keep this with &quot;if not&quot;.&nbsp; &quot;If it&#39;s not a file&quot; is a common case.<br>
<br><br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">&nbsp; &nbsp; &nbsp; &nbsp; $self-&gt;{chunks} = [];<br>
- &nbsp; &nbsp; &nbsp; &nbsp;return ();<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;return;<br>
 &nbsp; &nbsp; }</blockquote><div><br>This function is called in list context.&nbsp; I like to stay explicit about that in my return type.&nbsp; I don&#39;t want to rely on implicit casts.<br>&nbsp;<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">

- &nbsp; &nbsp;# Get the appropriate FileChunker for this file type,<br>
- &nbsp; &nbsp;# then pass ourselves to it to get our chunks.<br>
- &nbsp; &nbsp;my @chunk_list;<br>
+ &nbsp; &nbsp;my $fullpath = $self-&gt;fullpath;<br>
+ &nbsp; &nbsp;open(my $fh, &#39;&lt;&#39;, $fullpath)<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;or die &quot;Failed to open $fullpath: $!\n&quot;;<br>
+ &nbsp; &nbsp;binmode($fh);<br>
<br>
 &nbsp; &nbsp; # Get the appropriate FileChunker for this file type<br>
 &nbsp; &nbsp; my $chunker = $self-&gt;find_file_chunker();<br>
@@ -139,6 +145,11 @@ sub chunks {<br>
 &nbsp; &nbsp; my $last_offset = undef;<br>
 &nbsp; &nbsp; my $last_len = undef;<br>
<br>
+ &nbsp; &nbsp;my $sha1_file = new Digest::SHA1;<br>
+ &nbsp; &nbsp;my $sha1_chunk = new Digest::SHA1;<br>
+<br>
+ &nbsp; &nbsp;my @chunks;<br>
+<br>
 &nbsp; &nbsp; $chunker-&gt;get_offsets($self, sub {<br>
 &nbsp; &nbsp; &nbsp; &nbsp; my ($offset, $len) = @_;<br>
<br>
@@ -164,10 +175,31 @@ sub chunks {<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; die &quot;Chunker didn&#39;t start at offset 0&quot;;<br>
 &nbsp; &nbsp; &nbsp; &nbsp; }<br>
<br>
- &nbsp; &nbsp; &nbsp; &nbsp;push @chunk_list, new Brackup::PositionedChunk(</blockquote><div><br>Brackup::PositionedChunk-&gt;new<br>&nbsp;</div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;# Optimize for continous reading<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;unless (tell($fh) == $offset) {<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;seek($fh, $offset, 0) or die &quot;Couldn&#39;t seek: $!\n&quot;;<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;}<br>
+<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;my $data;<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;my $rv = read($fh, $data, $len) or die &quot;Failed to read: $!\n&quot;;<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;unless ($rv == $len) {<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Carp::confess(&quot;Read $rv bytes, not $len&quot;);<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;}<br>
+<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;# Update digest for complete file<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;$sha1_file-&gt;add($data);<br>
+<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;# Generate chunk digest<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;$sha1_chunk-&gt;reset();</blockquote><div><br>-&gt;reset;<br>&nbsp;<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><br>
+ &nbsp; &nbsp; &nbsp; &nbsp;$sha1_chunk-&gt;add($data);<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;my $chunk_digest = &#39;sha1:&#39; . $sha1_chunk-&gt;hexdigest();</blockquote><div><br>-&gt;hexdigest;<br>&nbsp;</div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<br>
+<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;push @chunks, new Brackup::PositionedChunk(<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; file =&gt; $self,<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; offset =&gt; $offset,<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; length =&gt; $len,<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;chunkref =&gt; \$data,<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;digest =&gt; $chunk_digest,<br>
 &nbsp; &nbsp; &nbsp; &nbsp; );</blockquote><div><br>Uh, you do this unconditionally?&nbsp; Not dependent on the size of the file!?&nbsp; How am I going to backup a huge file on a low-memory box?<br><br>Let&#39;s discuss that first before I review more.<br>
<br>- Brad<br><br><br>&nbsp;<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">&nbsp; &nbsp; &nbsp; &nbsp; $last_offset = $offset;<br>
@@ -178,46 +210,18 @@ sub chunks {<br>
 &nbsp; &nbsp; &nbsp; &nbsp; die &quot;Chunker didn&#39;t return complete file&quot;;<br>
 &nbsp; &nbsp; }<br>
<br>
- &nbsp; &nbsp;$self-&gt;{chunks} = \@chunk_list;<br>
- &nbsp; &nbsp;return @chunk_list;<br>
-}<br>
+ &nbsp; &nbsp;my $file_digest = &#39;sha1:&#39; . $sha1_file-&gt;hexdigest();<br>
<br>
-sub full_digest {<br>
- &nbsp; &nbsp;my $self = shift;<br>
- &nbsp; &nbsp;return $self-&gt;{_full_digest} ||= $self-&gt;_calc_full_digest;<br>
-}<br>
-<br>
-sub _calc_full_digest {<br>
- &nbsp; &nbsp;my $self = shift;<br>
- &nbsp; &nbsp;return &quot;&quot; unless $self-&gt;is_file;<br>
-<br>
- &nbsp; &nbsp;my $cache = $self-&gt;{root}-&gt;digest_cache;<br>
- &nbsp; &nbsp;my $key &nbsp; = $self-&gt;cachekey;<br>
-<br>
- &nbsp; &nbsp;my $dig = $cache-&gt;get($key);<br>
- &nbsp; &nbsp;return $dig if $dig;<br>
+ &nbsp; &nbsp;$self-&gt;{_full_digest} = $file_digest;<br>
<br>
- &nbsp; &nbsp;# legacy migration thing... we used to more often store<br>
- &nbsp; &nbsp;# the chunk digests, not the file digests. &nbsp;so try that<br>
- &nbsp; &nbsp;# first...<br>
- &nbsp; &nbsp;if ($self-&gt;chunks == 1) {<br>
- &nbsp; &nbsp; &nbsp; &nbsp;my ($chunk) = $self-&gt;chunks;<br>
- &nbsp; &nbsp; &nbsp; &nbsp;$dig = $cache-&gt;get($chunk-&gt;cachekey);<br>
- &nbsp; &nbsp;}<br>
-<br>
- &nbsp; &nbsp;unless ($dig) {<br>
- &nbsp; &nbsp; &nbsp; &nbsp;my $sha1 = Digest::SHA1-&gt;new;<br>
- &nbsp; &nbsp; &nbsp; &nbsp;my $path = $self-&gt;fullpath;<br>
- &nbsp; &nbsp; &nbsp; &nbsp;open (my $fh, $path) or die &quot;Couldn&#39;t open $path: $!\n&quot;;<br>
- &nbsp; &nbsp; &nbsp; &nbsp;binmode($fh);<br>
- &nbsp; &nbsp; &nbsp; &nbsp;$sha1-&gt;addfile($fh);<br>
- &nbsp; &nbsp; &nbsp; &nbsp;close($fh);<br>
+ &nbsp; &nbsp;close($fh);<br>
<br>
- &nbsp; &nbsp; &nbsp; &nbsp;$dig = &quot;sha1:&quot; . $sha1-&gt;hexdigest;<br>
- &nbsp; &nbsp;}<br>
+ &nbsp; &nbsp;$self-&gt;{chunks} = \@chunks;<br>
+}<br>
<br>
- &nbsp; &nbsp;$cache-&gt;set($key =&gt; $dig);<br>
- &nbsp; &nbsp;return $dig;<br>
+sub full_digest {<br>
+ &nbsp; &nbsp;my $self = shift;<br>
+ &nbsp; &nbsp;return $self-&gt;{_full_digest};<br>
&nbsp;}<br>
<br>
&nbsp;sub link_target {<br>
diff --git a/lib/Brackup/PositionedChunk.pm b/lib/Brackup/PositionedChunk.pm<br>
index b5fabc1..8ed806c 100644<br>
--- a/lib/Brackup/PositionedChunk.pm<br>
+++ b/lib/Brackup/PositionedChunk.pm<br>
@@ -20,6 +20,8 @@ sub new {<br>
 &nbsp; &nbsp; $self-&gt;{file} &nbsp; = delete $opts{&#39;file&#39;}; &nbsp; &nbsp;# Brackup::File object<br>
 &nbsp; &nbsp; $self-&gt;{offset} = delete $opts{&#39;offset&#39;};<br>
 &nbsp; &nbsp; $self-&gt;{length} = delete $opts{&#39;length&#39;};<br>
+ &nbsp; &nbsp;$self-&gt;{_raw_chunkref} = delete $opts{&#39;chunkref&#39;};<br>
+ &nbsp; &nbsp;$self-&gt;{_raw_digest} = delete $opts{&#39;digest&#39;};<br>
<br>
 &nbsp; &nbsp; croak(&quot;Unknown options: &quot; . join(&#39;, &#39;, keys %opts)) if %opts;<br>
 &nbsp; &nbsp; croak(&quot;offset not numeric&quot;) unless $self-&gt;{offset} =~ /^\d+$/;<br>
@@ -55,53 +57,12 @@ sub root {<br>
<br>
&nbsp;sub raw_digest {<br>
 &nbsp; &nbsp; my $self = shift;<br>
- &nbsp; &nbsp;return $self-&gt;{_raw_digest} ||= $self-&gt;_calc_raw_digest;<br>
-}<br>
-<br>
-sub _calc_raw_digest {<br>
- &nbsp; &nbsp;my $self = shift;<br>
-<br>
- &nbsp; &nbsp;my $n_chunks = $self-&gt;{file}-&gt;chunks<br>
- &nbsp; &nbsp; &nbsp; &nbsp;or die &quot;zero chunks?&quot;;<br>
- &nbsp; &nbsp;if ($n_chunks == 1) {<br>
- &nbsp; &nbsp; &nbsp; &nbsp;# don&#39;t calculate this chunk&#39;s digest.. it&#39;s the same as our<br>
- &nbsp; &nbsp; &nbsp; &nbsp;# file&#39;s digest, since this chunk spans the entire file.<br>
- &nbsp; &nbsp; &nbsp; &nbsp;die &quot;ASSERT&quot; unless $self-&gt;length == $self-&gt;{file}-&gt;size;<br>
- &nbsp; &nbsp; &nbsp; &nbsp;return $self-&gt;{file}-&gt;full_digest;<br>
- &nbsp; &nbsp;}<br>
-<br>
- &nbsp; &nbsp;my $cache = $self-&gt;root-&gt;digest_cache;<br>
- &nbsp; &nbsp;my $key &nbsp; = $self-&gt;cachekey;<br>
- &nbsp; &nbsp;my $dig;<br>
-<br>
- &nbsp; &nbsp;if ($dig = $cache-&gt;get($key)) {<br>
- &nbsp; &nbsp; &nbsp; &nbsp;return $self-&gt;{_raw_digest} = $dig;<br>
- &nbsp; &nbsp;}<br>
-<br>
- &nbsp; &nbsp;my $rchunk = $self-&gt;raw_chunkref;<br>
- &nbsp; &nbsp;$dig = &quot;sha1:&quot; . sha1_hex($$rchunk);<br>
-<br>
- &nbsp; &nbsp;$cache-&gt;set($key =&gt; $dig);<br>
-<br>
- &nbsp; &nbsp;return $self-&gt;{_raw_digest} = $dig;<br>
+ &nbsp; &nbsp;return $self-&gt;{_raw_digest};<br>
&nbsp;}<br>
<br>
&nbsp;sub raw_chunkref {<br>
 &nbsp; &nbsp; my $self = shift;<br>
- &nbsp; &nbsp;return $self-&gt;{_raw_chunkref} if $self-&gt;{_raw_chunkref};<br>
-<br>
- &nbsp; &nbsp;my $data;<br>
- &nbsp; &nbsp;my $fullpath = $self-&gt;{file}-&gt;fullpath;<br>
- &nbsp; &nbsp;open(my $fh, $fullpath) or die &quot;Failed to open $fullpath: $!\n&quot;;<br>
- &nbsp; &nbsp;binmode($fh);<br>
- &nbsp; &nbsp;seek($fh, $self-&gt;{offset}, 0) or die &quot;Couldn&#39;t seek: $!\n&quot;;<br>
- &nbsp; &nbsp;my $rv = read($fh, $data, $self-&gt;{length})<br>
- &nbsp; &nbsp; &nbsp; &nbsp;or die &quot;Failed to read: $!\n&quot;;<br>
- &nbsp; &nbsp;unless ($rv == $self-&gt;{length}) {<br>
- &nbsp; &nbsp; &nbsp; &nbsp;Carp::confess(&quot;Read $rv bytes, not $self-&gt;{length}&quot;);<br>
- &nbsp; &nbsp;}<br>
-<br>
- &nbsp; &nbsp;return $self-&gt;{_raw_chunkref} = \$data;<br>
+ &nbsp; &nbsp;return $self-&gt;{_raw_chunkref};<br>
&nbsp;}<br>
<br>
&nbsp;# useful string for targets to key on. &nbsp;of one of the forms:<br>
<font color="#888888">--<br>
<a href="http://1.5.4.5" target="_blank">1.5.4.5</a><br>
<br>
</font></blockquote></div><br>