rework and fix one-time download
authorLunar <lunar@anargeek.net>
Sun, 8 Aug 2010 15:23:32 +0000 (17:23 +0200)
committerLunar <lunar@anargeek.net>
Sun, 8 Aug 2010 15:23:32 +0000 (17:23 +0200)
README
coquelicot_app.rb
lib/coquelicot.rb
views/index.haml

diff --git a/README b/README
index 2490e95..a01b961 100644 (file)
--- a/README
+++ b/README
@@ -31,7 +31,8 @@ Features
  * Support for one-time download
 
    An user might want to allow exactly _one_ download of a file,
-   to more closely replace an email attachment.
+   to more closely replace an email attachment. The file will be removed after
+   the first complete download and concurrent download are prevented.
 
  * Upload progress bar
 
index 8168c01..42f6ded 100644 (file)
@@ -97,17 +97,51 @@ def send_stored_file(file)
   throw :halt, [200, file]
 end
 
+module Coquelicot
+  class StoredFile
+    def lockfile
+      @lockfile ||= Lockfile.new "#{File.expand_path(@path)}.lock", :timeout => 4
+    end
+
+    def each
+      # output content
+      yield @initial_content
+      @initial_content = nil
+      until (buf = @file.read(BUFFER_LEN)).nil?
+        yield @cipher.update(buf)
+      end
+      yield @cipher.final
+      @fully_sent = true
+    end
+
+    def close
+      if @cipher
+        @cipher.reset
+        @cipher = nil
+      end
+      @file.close
+      if one_time_only?
+        empty! if @fully_sent
+        lockfile.unlock
+      end
+    end
+  end
+end
+
 def send_link(link, pass)
   file = Coquelicot.depot.get_file(link, pass)
   return false if file.nil?
   return expired if file.expired?
 
-  return send_stored_file(file) unless file.one_time_only?
-
-  file.exclusively do
-    begin  send_stored_file(file)
-    ensure file.empty!            end
+  if file.one_time_only?
+    begin
+      # unlocking done in file.close
+      file.lockfile.lock
+    rescue Lockfile::TimeoutLockError
+      error 409, "Download currently in progress"
+    end
   end
+  send_stored_file(file)
 end
 
 get '/:link-:pass' do |link, pass|
index 2f199ef..18f99c1 100644 (file)
@@ -15,18 +15,6 @@ module Coquelicot
       StoredFile.new(path, pass)
     end
 
-    def each
-      # output content
-      yield @initial_content
-      @initial_content = nil
-      until (buf = @file.read(BUFFER_LEN)).nil?
-        yield @cipher.update(buf)
-      end
-      yield @cipher.final
-      @cipher.reset
-      @cipher = nil
-    end
-
     def created_at
       Time.at(@meta['Created-at'])
     end
@@ -39,21 +27,6 @@ module Coquelicot
       @meta['One-time-only'] && @meta['One-time-only'] == 'true'
     end
 
-    def exclusively(&block)
-      old_path = @path
-      begin
-        new_path = "#{old_path}.#{Coquelicot.gen_random_base32(16)}"
-      end while File.exists? new_path
-      File.rename(old_path, new_path)
-      @path = new_path
-      File.open(old_path, 'w').close
-      begin
-        yield
-      ensure
-        File.rename(new_path, old_path)
-      end
-    end
-
     def self.create(src, pass, meta)
       salt = gen_salt
       clear_meta = { "Coquelicot" => COQUELICOT_VERSION,
@@ -163,19 +136,9 @@ module Coquelicot
       @initial_content = block[1]
       @meta.merge! YAML.load(yaml)
     end
-
-    def close
-      @cipher.reset unless @cipher.nil?
-      @file.close
-    end
   end
 
   class Depot
-    LOCKFILE_OPTIONS = { :timeout => 60,
-                         :max_age => 8,
-                         :refresh => 2,
-                         :debug   => false }
-
     attr_reader :path
 
     def initialize(path)
@@ -227,6 +190,11 @@ module Coquelicot
 
   private
 
+    LOCKFILE_OPTIONS = { :timeout => 60,
+                         :max_age => 8,
+                         :refresh => 2,
+                         :debug   => false }
+
     def lockfile
       Lockfile.new "#{@path}/.lock", LOCKFILE_OPTIONS
     end
index 3694cf8..319c878 100644 (file)
@@ -19,7 +19,7 @@
       %option{ :value => 60 * 24 * 30 } 1 month
   .field
     %label One time download:
-    %input{ :type => 'checkbox', :id => 'one_time', :name => 'one_time', :value => true }
+    %input{ :type => 'checkbox', :id => 'one_time', :name => 'one_time', :value => 'true' }
     %label{ :for => 'one_time' } Remove after one download
   .field
     %label{ :for => 'file_key' } Download password: