store.rb 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
  2. require 'digest/md5'
  3. class Store < ApplicationModel
  4. store :preferences
  5. belongs_to :store_object, :class_name => 'Store::Object'
  6. belongs_to :store_file, :class_name => 'Store::File'
  7. validates :filename, :presence => true
  8. =begin
  9. add an attachment to storage
  10. result = Store.add(
  11. :object => 'Ticket::Article',
  12. :o_id => 4711,
  13. :data => binary_string,
  14. :preferences => {
  15. :content_type => 'image/png',
  16. :content_id => 234,
  17. }
  18. )
  19. returns
  20. result = true
  21. =end
  22. def self.add(data)
  23. data = data.stringify_keys
  24. # lookup store_object.id
  25. store_object = Store::Object.create_if_not_exists( :name => data['object'] )
  26. data['store_object_id'] = store_object.id
  27. # check if record already exists
  28. # store = Store.where( :store_object_id => store_object.id, :o_id => data['o_id'], ).first
  29. # if store != nil
  30. # return store
  31. # end
  32. # check real store
  33. md5 = Digest::MD5.hexdigest( data['data'] )
  34. data['size'] = data['data'].to_s.bytesize
  35. # file = Store::Provider::DB.create( data['data'], md5 )
  36. file = Store::File.where( :md5 => md5 ).first
  37. # store attachment
  38. if file == nil
  39. file = Store::File.create(
  40. :data => data['data'],
  41. :md5 => md5,
  42. )
  43. end
  44. data['store_file_id'] = file.id
  45. # not needed attributes
  46. data.delete('data')
  47. data.delete('object')
  48. # store meta data
  49. store = Store.create(data)
  50. true
  51. end
  52. =begin
  53. get attachment of object
  54. list = Store.list(
  55. :object => 'Ticket::Article',
  56. :o_id => 4711,
  57. )
  58. returns
  59. result = [store1, store2]
  60. store1 = {
  61. :size => 94123,
  62. :filename => 'image.png',
  63. :preferences => {
  64. :content_type => 'image/png',
  65. :content_id => 234,
  66. }
  67. }
  68. store1.content # binary_string
  69. =end
  70. def self.list(data)
  71. # search
  72. store_object_id = Store::Object.lookup( :name => data[:object] )
  73. stores = Store.where( :store_object_id => store_object_id, :o_id => data[:o_id].to_i ).
  74. order('created_at ASC, id ASC')
  75. return stores
  76. end
  77. =begin
  78. remove an attachment to storage
  79. result = Store.remove(
  80. :object => 'Ticket::Article',
  81. :o_id => 4711,
  82. )
  83. returns
  84. result = true
  85. =end
  86. def self.remove(data)
  87. # search
  88. store_object_id = Store::Object.lookup( :name => data[:object] )
  89. stores = Store.where( :store_object_id => store_object_id ).
  90. where( :o_id => data[:o_id] ).
  91. order('created_at ASC, id ASC')
  92. stores.each do |store|
  93. # check backend for references
  94. files = Store.where( :store_file_id => store.store_file_id )
  95. if files.count == 1 && files.first.id == store.id
  96. # file = Store::Provider::DB.delete( store.store_file_id )
  97. Store::File.find( store.store_file_id ).destroy
  98. end
  99. store.destroy
  100. end
  101. return true
  102. end
  103. # get attachment
  104. def content
  105. # Store::Provider::DB.content( store.store_file_id )
  106. file = Store::File.where( :id => self.store_file_id ).first
  107. return if !file
  108. if file.file_system
  109. return file.read_from_fs
  110. end
  111. file.data
  112. end
  113. end
  114. class Store::Object < ApplicationModel
  115. validates :name, :presence => true
  116. end
  117. class Store::File < ApplicationModel
  118. before_validation :add_md5
  119. before_create :check_location
  120. after_destroy :unlink_location
  121. # generate file location
  122. def get_locaton
  123. # generate directory
  124. base = Rails.root.to_s + '/storage/fs/'
  125. parts = self.md5.scan(/.{1,3}/)
  126. path = parts[ 1 .. 7 ].join('/') + '/'
  127. file = parts[ 8 .. parts.count ].join('')
  128. location = "#{base}/#{path}"
  129. # create directory if not exists
  130. if !File.exist?( location )
  131. FileUtils.mkdir_p( location )
  132. end
  133. location += file
  134. end
  135. # read file from fs
  136. def unlink_location
  137. if File.exist?( self.get_locaton )
  138. puts "NOTICE: storge remove '#{self.get_locaton}'"
  139. File.delete( self.get_locaton )
  140. end
  141. end
  142. # read file from fs
  143. def read_from_fs
  144. puts "read from fs #{self.get_locaton}"
  145. return if !File.exist?( self.get_locaton )
  146. data = File.open( self.get_locaton, 'rb' )
  147. content = data.read
  148. # check md5
  149. md5 = Digest::MD5.hexdigest( content )
  150. if md5 != self.md5
  151. raise "ERROR: Corrupt file in fs #{self.get_locaton}, md5 should be #{self.md5} but is #{md5}"
  152. end
  153. content
  154. end
  155. # write file to fs
  156. def write_to_fs
  157. # install file
  158. permission = '600'
  159. if !File.exist?( self.get_locaton )
  160. puts "NOTICE: storge write '#{self.get_locaton}' (#{permission})"
  161. file = File.new( self.get_locaton, 'wb' )
  162. file.write( self.data )
  163. file.close
  164. end
  165. File.chmod( permission.to_i(8), self.get_locaton )
  166. # check md5
  167. md5 = Digest::MD5.hexdigest( self.read_from_fs )
  168. if md5 != self.md5
  169. raise "ERROR: Corrupt file in fs #{self.get_locaton}, md5 should be #{self.md5} but is #{md5}"
  170. end
  171. true
  172. end
  173. # write file to db
  174. def write_to_db
  175. # read and check md5
  176. content = self.read_from_fs
  177. # store in database
  178. self.data = content
  179. self.save
  180. # check md5 against db content
  181. md5 = Digest::MD5.hexdigest( self.data )
  182. if md5 != self.md5
  183. raise "ERROR: Corrupt file in db #{self.get_locaton}, md5 should be #{self.md5} but is #{md5}"
  184. end
  185. true
  186. end
  187. # check database data and md5, in case fix it
  188. def self.db_check_md5(fix_it = nil)
  189. Store::File.where( :file_system => false ).each {|item|
  190. md5 = Digest::MD5.hexdigest( item.data )
  191. if md5 != item.md5
  192. puts "DIFF: md5 diff of Store::File.find(#{item.id}) "
  193. if fix_it
  194. item.update_attribute( :md5, md5 )
  195. end
  196. end
  197. }
  198. true
  199. end
  200. def self.fs_check_md5(fix_it = nil)
  201. Store::File.where( :file_system => true ).each {|item|
  202. md5 = Digest::MD5.hexdigest( item.read_from_fs )
  203. if md5 != item.md5
  204. puts "DIFF: md5 diff of Store::File.find(#{item.id}) "
  205. if fix_it
  206. item.update_attribute( :md5, md5 )
  207. end
  208. end
  209. }
  210. true
  211. end
  212. def self.move_to_fs
  213. Store::File.where( :file_system => false ).each {|item|
  214. item.write_to_fs
  215. item.update_attribute( :file_system, true )
  216. item.update_attribute( :data, nil )
  217. }
  218. end
  219. def self.move_to_db
  220. Store::File.where( :file_system => true ).each {|item|
  221. item.write_to_db
  222. item.update_attribute( :file_system, false )
  223. item.unlink_location
  224. }
  225. end
  226. private
  227. def check_location
  228. # write initial to fs if needed
  229. if self.file_system && self.data
  230. self.write_to_fs
  231. self.data = nil
  232. end
  233. end
  234. def add_md5
  235. if self.data && !self.md5
  236. self.md5 = Digest::MD5.hexdigest( self.data )
  237. end
  238. end
  239. end