public
Description: Paperclip File Management Plugin
Homepage: http://www.thoughtbot.com/projects/paperclip
Clone URL: git://github.com/thoughtbot/paperclip.git
paperclip / lib / paperclip / storage.rb
100644 180 lines (163 sloc) 6.939 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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
180
module Paperclip
  module Storage
 
    # The default place to store attachments is in the filesystem. Files on the local
    # filesystem can be very easily served by Apache without requiring a hit to your app.
    # They also can be processed more easily after they've been saved, as they're just
    # normal files. There is one Filesystem-specific option for has_attached_file.
    # * +path+: The location of the repository of attachments on disk. This can (and, in
    # almost all cases, should) be coordinated with the value of the +url+ option to
    # allow files to be saved into a place where Apache can serve them without
    # hitting your app. Defaults to
    # ":rails_root/public/:class/:attachment/:id/:style_:filename".
    # By default this places the files in the app's public directory which can be served
    # directly. If you are using capistrano for deployment, a good idea would be to
    # make a symlink to the capistrano-created system directory from inside your app's
    # public directory.
    # See Paperclip::Attachment#interpolate for more information on variable interpolaton.
    # :path => "/var/app/attachments/:class/:id/:style/:filename"
    module Filesystem
      def self.extended base
      end
      
      def exists?(style = default_style)
        if original_filename
          File.exist?(path(style))
        else
          false
        end
      end
 
      # Returns representation of the data of the file assigned to the given
      # style, in the format most representative of the current storage.
      def to_file style = default_style
        @queued_for_write[style] || (File.new(path(style)) if exists?(style))
      end
      alias_method :to_io, :to_file
 
      def flush_writes #:nodoc:
        @queued_for_write.each do |style, file|
          FileUtils.mkdir_p(File.dirname(path(style)))
          result = file.stream_to(path(style))
          file.close
          result.close
        end
        @queued_for_write = {}
      end
 
      def flush_deletes #:nodoc:
        @queued_for_delete.each do |path|
          begin
            FileUtils.rm(path) if File.exist?(path)
          rescue Errno::ENOENT => e
            # ignore file-not-found, let everything else pass
          end
        end
        @queued_for_delete = []
      end
    end
 
    # Amazon's S3 file hosting service is a scalable, easy place to store files for
    # distribution. You can find out more about it at http://aws.amazon.com/s3
    # There are a few S3-specific options for has_attached_file:
    # * +s3_credentials+: Takes a path, a File, or a Hash. The path (or File) must point
    # to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon
    # gives you. You can 'environment-space' this just like you do to your
    # database.yml file, so different environments can use different accounts:
    # development:
    # access_key_id: 123...
    # secret_access_key: 123...
    # test:
    # access_key_id: abc...
    # secret_access_key: abc...
    # production:
    # access_key_id: 456...
    # secret_access_key: 456...
    # This is not required, however, and the file may simply look like this:
    # access_key_id: 456...
    # secret_access_key: 456...
    # In which case, those access keys will be used in all environments.
    # * +s3_permissions+: This is a String that should be one of the "canned" access
    # policies that S3 provides (more information can be found here:
    # http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAccessPolicy.html#RESTCannedAccessPolicies)
    # The default for Paperclip is "public-read".
    # * +bucket+: This is the name of the S3 bucket that will store your files. Remember
    # that the bucket must be unique across all of Amazon S3. If the bucket does not exist
    # Paperclip will attempt to create it. The bucket name will not be interpolated.
    # * +path+: This is the key under the bucket in which the file will be stored. The
    # URL will be constructed from the bucket and the path. This is what you will want
    # to interpolate. Keys should be unique, like filenames, and despite the fact that
    # S3 (strictly speaking) does not support directories, you can still use a / to
    # separate parts of your file name.
    module S3
      def self.extended base
        require 'right_aws'
        base.instance_eval do
          @bucket = @options[:bucket]
          @s3_credentials = parse_credentials(@options[:s3_credentials])
          @s3_options = @options[:s3_options] || {}
          @s3_permissions = @options[:s3_permissions] || 'public-read'
          @url = ":s3_url"
        end
        base.class.interpolations[:s3_url] = lambda do |attachment, style|
          "https://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
        end
      end
 
      def s3
        @s3 ||= RightAws::S3.new(@s3_credentials[:access_key_id],
                                 @s3_credentials[:secret_access_key],
                                 @s3_options)
      end
 
      def s3_bucket
        @s3_bucket ||= s3.bucket(@bucket, true, @s3_permissions)
      end
 
      def bucket_name
        @bucket
      end
 
      def parse_credentials creds
        creds = find_credentials(creds).stringify_keys
        (creds[ENV['RAILS_ENV']] || creds).symbolize_keys
      end
      
      def exists?(style = default_style)
        s3_bucket.key(path(style)) ? true : false
      end
 
      # Returns representation of the data of the file assigned to the given
      # style, in the format most representative of the current storage.
      def to_file style = default_style
        @queued_for_write[style] || s3_bucket.key(path(style))
      end
      alias_method :to_io, :to_file
 
      def flush_writes #:nodoc:
        @queued_for_write.each do |style, file|
          begin
            key = s3_bucket.key(path(style))
            key.data = file
            key.put(nil, @s3_permissions)
          rescue RightAws::AwsError => e
            raise
          end
        end
        @queued_for_write = {}
      end
 
      def flush_deletes #:nodoc:
        @queued_for_delete.each do |path|
          begin
            if file = s3_bucket.key(path)
              file.delete
            end
          rescue RightAws::AwsError
            # Ignore this.
          end
        end
        @queued_for_delete = []
      end
      
      def find_credentials creds
        case creds
        when File:
          YAML.load_file(creds.path)
        when String:
          YAML.load_file(creds)
        when Hash:
          creds
        else
          raise ArgumentError, "Credentials are not a path, file, or hash."
        end
      end
      private :find_credentials
 
    end
  end
end