##// END OF EJS Templates
remove trailing white-spaces from app/models/attachment.rb....
Toshi MARUYAMA -
r6406:5a7c888df989
parent child
Show More
@@ -1,193 +1,193
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "digest/md5"
18 require "digest/md5"
19
19
20 class Attachment < ActiveRecord::Base
20 class Attachment < ActiveRecord::Base
21 belongs_to :container, :polymorphic => true
21 belongs_to :container, :polymorphic => true
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
23
23
24 validates_presence_of :container, :filename, :author
24 validates_presence_of :container, :filename, :author
25 validates_length_of :filename, :maximum => 255
25 validates_length_of :filename, :maximum => 255
26 validates_length_of :disk_filename, :maximum => 255
26 validates_length_of :disk_filename, :maximum => 255
27
27
28 acts_as_event :title => :filename,
28 acts_as_event :title => :filename,
29 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
29 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
30
30
31 acts_as_activity_provider :type => 'files',
31 acts_as_activity_provider :type => 'files',
32 :permission => :view_files,
32 :permission => :view_files,
33 :author_key => :author_id,
33 :author_key => :author_id,
34 :find_options => {:select => "#{Attachment.table_name}.*",
34 :find_options => {:select => "#{Attachment.table_name}.*",
35 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
35 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
36 "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id OR ( #{Attachment.table_name}.container_type='Project' AND #{Attachment.table_name}.container_id = #{Project.table_name}.id )"}
36 "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id OR ( #{Attachment.table_name}.container_type='Project' AND #{Attachment.table_name}.container_id = #{Project.table_name}.id )"}
37
37
38 acts_as_activity_provider :type => 'documents',
38 acts_as_activity_provider :type => 'documents',
39 :permission => :view_documents,
39 :permission => :view_documents,
40 :author_key => :author_id,
40 :author_key => :author_id,
41 :find_options => {:select => "#{Attachment.table_name}.*",
41 :find_options => {:select => "#{Attachment.table_name}.*",
42 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
42 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
43 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
43 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
44
44
45 cattr_accessor :storage_path
45 cattr_accessor :storage_path
46 @@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{Rails.root}/files"
46 @@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{Rails.root}/files"
47
47
48 def validate
48 def validate
49 if self.filesize > Setting.attachment_max_size.to_i.kilobytes
49 if self.filesize > Setting.attachment_max_size.to_i.kilobytes
50 errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
50 errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
51 end
51 end
52 end
52 end
53
53
54 def file=(incoming_file)
54 def file=(incoming_file)
55 unless incoming_file.nil?
55 unless incoming_file.nil?
56 @temp_file = incoming_file
56 @temp_file = incoming_file
57 if @temp_file.size > 0
57 if @temp_file.size > 0
58 self.filename = sanitize_filename(@temp_file.original_filename)
58 self.filename = sanitize_filename(@temp_file.original_filename)
59 self.disk_filename = Attachment.disk_filename(filename)
59 self.disk_filename = Attachment.disk_filename(filename)
60 self.content_type = @temp_file.content_type.to_s.chomp
60 self.content_type = @temp_file.content_type.to_s.chomp
61 if content_type.blank?
61 if content_type.blank?
62 self.content_type = Redmine::MimeType.of(filename)
62 self.content_type = Redmine::MimeType.of(filename)
63 end
63 end
64 self.filesize = @temp_file.size
64 self.filesize = @temp_file.size
65 end
65 end
66 end
66 end
67 end
67 end
68
68
69 def file
69 def file
70 nil
70 nil
71 end
71 end
72
72
73 # Copies the temporary file to its final location
73 # Copies the temporary file to its final location
74 # and computes its MD5 hash
74 # and computes its MD5 hash
75 def before_save
75 def before_save
76 if @temp_file && (@temp_file.size > 0)
76 if @temp_file && (@temp_file.size > 0)
77 logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
77 logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
78 md5 = Digest::MD5.new
78 md5 = Digest::MD5.new
79 File.open(diskfile, "wb") do |f|
79 File.open(diskfile, "wb") do |f|
80 buffer = ""
80 buffer = ""
81 while (buffer = @temp_file.read(8192))
81 while (buffer = @temp_file.read(8192))
82 f.write(buffer)
82 f.write(buffer)
83 md5.update(buffer)
83 md5.update(buffer)
84 end
84 end
85 end
85 end
86 self.digest = md5.hexdigest
86 self.digest = md5.hexdigest
87 end
87 end
88 @temp_file = nil
88 @temp_file = nil
89 # Don't save the content type if it's longer than the authorized length
89 # Don't save the content type if it's longer than the authorized length
90 if self.content_type && self.content_type.length > 255
90 if self.content_type && self.content_type.length > 255
91 self.content_type = nil
91 self.content_type = nil
92 end
92 end
93 end
93 end
94
94
95 # Deletes file on the disk
95 # Deletes file on the disk
96 def after_destroy
96 def after_destroy
97 File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
97 File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
98 end
98 end
99
99
100 # Returns file's location on disk
100 # Returns file's location on disk
101 def diskfile
101 def diskfile
102 "#{@@storage_path}/#{self.disk_filename}"
102 "#{@@storage_path}/#{self.disk_filename}"
103 end
103 end
104
104
105 def increment_download
105 def increment_download
106 increment!(:downloads)
106 increment!(:downloads)
107 end
107 end
108
108
109 def project
109 def project
110 container.project
110 container.project
111 end
111 end
112
112
113 def visible?(user=User.current)
113 def visible?(user=User.current)
114 container.attachments_visible?(user)
114 container.attachments_visible?(user)
115 end
115 end
116
116
117 def deletable?(user=User.current)
117 def deletable?(user=User.current)
118 container.attachments_deletable?(user)
118 container.attachments_deletable?(user)
119 end
119 end
120
120
121 def image?
121 def image?
122 self.filename =~ /\.(jpe?g|gif|png)$/i
122 self.filename =~ /\.(jpe?g|gif|png)$/i
123 end
123 end
124
124
125 def is_text?
125 def is_text?
126 Redmine::MimeType.is_type?('text', filename)
126 Redmine::MimeType.is_type?('text', filename)
127 end
127 end
128
128
129 def is_diff?
129 def is_diff?
130 self.filename =~ /\.(patch|diff)$/i
130 self.filename =~ /\.(patch|diff)$/i
131 end
131 end
132
132
133 # Returns true if the file is readable
133 # Returns true if the file is readable
134 def readable?
134 def readable?
135 File.readable?(diskfile)
135 File.readable?(diskfile)
136 end
136 end
137
137
138 # Bulk attaches a set of files to an object
138 # Bulk attaches a set of files to an object
139 #
139 #
140 # Returns a Hash of the results:
140 # Returns a Hash of the results:
141 # :files => array of the attached files
141 # :files => array of the attached files
142 # :unsaved => array of the files that could not be attached
142 # :unsaved => array of the files that could not be attached
143 def self.attach_files(obj, attachments)
143 def self.attach_files(obj, attachments)
144 attached = []
144 attached = []
145 if attachments && attachments.is_a?(Hash)
145 if attachments && attachments.is_a?(Hash)
146 attachments.each_value do |attachment|
146 attachments.each_value do |attachment|
147 file = attachment['file']
147 file = attachment['file']
148 next unless file && file.size > 0
148 next unless file && file.size > 0
149 a = Attachment.create(:container => obj,
149 a = Attachment.create(:container => obj,
150 :file => file,
150 :file => file,
151 :description => attachment['description'].to_s.strip,
151 :description => attachment['description'].to_s.strip,
152 :author => User.current)
152 :author => User.current)
153 obj.attachments << a
153 obj.attachments << a
154
154
155 if a.new_record?
155 if a.new_record?
156 obj.unsaved_attachments ||= []
156 obj.unsaved_attachments ||= []
157 obj.unsaved_attachments << a
157 obj.unsaved_attachments << a
158 else
158 else
159 attached << a
159 attached << a
160 end
160 end
161 end
161 end
162 end
162 end
163 {:files => attached, :unsaved => obj.unsaved_attachments}
163 {:files => attached, :unsaved => obj.unsaved_attachments}
164 end
164 end
165
165
166 private
166 private
167 def sanitize_filename(value)
167 def sanitize_filename(value)
168 # get only the filename, not the whole path
168 # get only the filename, not the whole path
169 just_filename = value.gsub(/^.*(\\|\/)/, '')
169 just_filename = value.gsub(/^.*(\\|\/)/, '')
170 # NOTE: File.basename doesn't work right with Windows paths on Unix
170 # NOTE: File.basename doesn't work right with Windows paths on Unix
171 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
171 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
172
172
173 # Finally, replace all non alphanumeric, hyphens or periods with underscore
173 # Finally, replace all non alphanumeric, hyphens or periods with underscore
174 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
174 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
175 end
175 end
176
176
177 # Returns an ASCII or hashed filename
177 # Returns an ASCII or hashed filename
178 def self.disk_filename(filename)
178 def self.disk_filename(filename)
179 timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
179 timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
180 ascii = ''
180 ascii = ''
181 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
181 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
182 ascii = filename
182 ascii = filename
183 else
183 else
184 ascii = Digest::MD5.hexdigest(filename)
184 ascii = Digest::MD5.hexdigest(filename)
185 # keep the extension if any
185 # keep the extension if any
186 ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
186 ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
187 end
187 end
188 while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}"))
188 while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}"))
189 timestamp.succ!
189 timestamp.succ!
190 end
190 end
191 "#{timestamp}_#{ascii}"
191 "#{timestamp}_#{ascii}"
192 end
192 end
193 end
193 end
General Comments 0
You need to be logged in to leave comments. Login now