2014-02-03 19:23:00 +00:00
|
|
|
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
|
2013-06-12 15:59:58 +00:00
|
|
|
|
2012-04-10 14:06:46 +00:00
|
|
|
require 'digest/md5'
|
|
|
|
|
2013-01-24 08:18:29 +00:00
|
|
|
class Store < ApplicationModel
|
2012-10-14 21:00:33 +00:00
|
|
|
store :preferences
|
|
|
|
belongs_to :store_object, :class_name => 'Store::Object'
|
|
|
|
belongs_to :store_file, :class_name => 'Store::File'
|
|
|
|
validates :filename, :presence => true
|
|
|
|
|
2014-04-28 07:44:36 +00:00
|
|
|
=begin
|
|
|
|
|
|
|
|
add an attachment to storage
|
|
|
|
|
|
|
|
result = Store.add(
|
|
|
|
:object => 'Ticket::Article',
|
|
|
|
:o_id => 4711,
|
|
|
|
:data => binary_string,
|
|
|
|
:preferences => {
|
|
|
|
:content_type => 'image/png',
|
|
|
|
:content_id => 234,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
result = true
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
2012-04-10 14:06:46 +00:00
|
|
|
def self.add(data)
|
|
|
|
data = data.stringify_keys
|
|
|
|
|
|
|
|
# lookup store_object.id
|
2013-01-24 08:18:29 +00:00
|
|
|
store_object = Store::Object.create_if_not_exists( :name => data['object'] )
|
2012-04-10 14:06:46 +00:00
|
|
|
data['store_object_id'] = store_object.id
|
2012-12-02 10:18:55 +00:00
|
|
|
|
2012-04-10 14:06:46 +00:00
|
|
|
# check if record already exists
|
2013-06-12 15:59:58 +00:00
|
|
|
# store = Store.where( :store_object_id => store_object.id, :o_id => data['o_id'], ).first
|
|
|
|
# if store != nil
|
|
|
|
# return store
|
|
|
|
# end
|
2012-12-02 10:18:55 +00:00
|
|
|
|
2012-04-10 14:06:46 +00:00
|
|
|
# check real store
|
|
|
|
md5 = Digest::MD5.hexdigest( data['data'] )
|
2013-01-24 08:18:29 +00:00
|
|
|
data['size'] = data['data'].to_s.bytesize
|
2012-04-10 14:06:46 +00:00
|
|
|
|
2014-05-02 09:03:06 +00:00
|
|
|
# file = Store::Provider::DB.create( data['data'], md5 )
|
|
|
|
|
2012-04-10 14:06:46 +00:00
|
|
|
file = Store::File.where( :md5 => md5 ).first
|
2012-12-02 10:18:55 +00:00
|
|
|
|
2012-04-10 14:06:46 +00:00
|
|
|
# store attachment
|
|
|
|
if file == nil
|
|
|
|
file = Store::File.create(
|
|
|
|
:data => data['data'],
|
2014-04-28 07:44:36 +00:00
|
|
|
:md5 => md5,
|
2012-04-10 14:06:46 +00:00
|
|
|
)
|
|
|
|
end
|
2012-12-02 10:18:55 +00:00
|
|
|
|
2012-04-10 14:06:46 +00:00
|
|
|
data['store_file_id'] = file.id
|
|
|
|
|
|
|
|
# not needed attributes
|
|
|
|
data.delete('data')
|
|
|
|
data.delete('object')
|
|
|
|
|
|
|
|
# store meta data
|
|
|
|
store = Store.create(data)
|
|
|
|
|
2014-04-28 07:44:36 +00:00
|
|
|
true
|
2012-04-10 14:06:46 +00:00
|
|
|
end
|
2012-12-02 10:18:55 +00:00
|
|
|
|
2014-04-28 07:44:36 +00:00
|
|
|
=begin
|
|
|
|
|
|
|
|
get attachment of object
|
|
|
|
|
|
|
|
list = Store.list(
|
|
|
|
:object => 'Ticket::Article',
|
|
|
|
:o_id => 4711,
|
|
|
|
)
|
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
result = [store1, store2]
|
|
|
|
|
|
|
|
store1 = {
|
|
|
|
:size => 94123,
|
|
|
|
:filename => 'image.png',
|
|
|
|
:preferences => {
|
|
|
|
:content_type => 'image/png',
|
|
|
|
:content_id => 234,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
store1.content # binary_string
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
2012-04-10 14:06:46 +00:00
|
|
|
def self.list(data)
|
|
|
|
# search
|
2013-05-10 21:48:29 +00:00
|
|
|
store_object_id = Store::Object.lookup( :name => data[:object] )
|
|
|
|
stores = Store.where( :store_object_id => store_object_id, :o_id => data[:o_id].to_i ).
|
2013-06-12 15:59:58 +00:00
|
|
|
order('created_at ASC, id ASC')
|
2012-04-10 14:06:46 +00:00
|
|
|
return stores
|
|
|
|
end
|
|
|
|
|
2014-04-28 07:44:36 +00:00
|
|
|
=begin
|
|
|
|
|
|
|
|
remove an attachment to storage
|
|
|
|
|
|
|
|
result = Store.remove(
|
|
|
|
:object => 'Ticket::Article',
|
|
|
|
:o_id => 4711,
|
|
|
|
)
|
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
result = true
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
2012-04-10 14:06:46 +00:00
|
|
|
def self.remove(data)
|
|
|
|
# search
|
2013-05-10 21:48:29 +00:00
|
|
|
store_object_id = Store::Object.lookup( :name => data[:object] )
|
|
|
|
stores = Store.where( :store_object_id => store_object_id ).
|
2013-06-12 15:59:58 +00:00
|
|
|
where( :o_id => data[:o_id] ).
|
|
|
|
order('created_at ASC, id ASC')
|
2012-04-10 14:06:46 +00:00
|
|
|
stores.each do |store|
|
2014-04-28 07:44:36 +00:00
|
|
|
|
|
|
|
# check backend for references
|
|
|
|
files = Store.where( :store_file_id => store.store_file_id )
|
|
|
|
if files.count == 1 && files.first.id == store.id
|
2014-05-02 09:03:06 +00:00
|
|
|
# file = Store::Provider::DB.delete( store.store_file_id )
|
2014-04-28 07:44:36 +00:00
|
|
|
Store::File.find( store.store_file_id ).destroy
|
|
|
|
end
|
|
|
|
|
2012-04-10 14:06:46 +00:00
|
|
|
store.destroy
|
|
|
|
end
|
2012-12-02 10:18:55 +00:00
|
|
|
return true
|
2012-04-10 14:06:46 +00:00
|
|
|
end
|
|
|
|
|
2014-04-28 07:44:36 +00:00
|
|
|
# get attachment
|
|
|
|
def content
|
2014-05-02 09:03:06 +00:00
|
|
|
# Store::Provider::DB.content( store.store_file_id )
|
2014-04-28 07:44:36 +00:00
|
|
|
file = Store::File.where( :id => self.store_file_id ).first
|
|
|
|
return if !file
|
|
|
|
if file.file_system
|
|
|
|
return file.read_from_fs
|
|
|
|
end
|
|
|
|
file.data
|
2012-04-10 14:06:46 +00:00
|
|
|
end
|
2014-04-28 07:44:36 +00:00
|
|
|
end
|
2012-04-10 14:06:46 +00:00
|
|
|
|
2014-04-28 07:44:36 +00:00
|
|
|
class Store::Object < ApplicationModel
|
|
|
|
validates :name, :presence => true
|
|
|
|
end
|
2012-04-10 14:06:46 +00:00
|
|
|
|
2014-04-28 07:44:36 +00:00
|
|
|
class Store::File < ApplicationModel
|
|
|
|
before_validation :add_md5
|
|
|
|
before_create :check_location
|
2014-04-28 17:24:47 +00:00
|
|
|
after_destroy :unlink_location
|
2014-04-28 07:44:36 +00:00
|
|
|
|
|
|
|
# generate file location
|
|
|
|
def get_locaton
|
|
|
|
|
|
|
|
# generate directory
|
2014-04-28 19:20:09 +00:00
|
|
|
base = Rails.root.to_s + '/storage/fs/'
|
|
|
|
parts = self.md5.scan(/.{1,3}/)
|
|
|
|
path = parts[ 1 .. 7 ].join('/') + '/'
|
|
|
|
file = parts[ 8 .. parts.count ].join('')
|
|
|
|
location = "#{base}/#{path}"
|
2014-04-28 07:44:36 +00:00
|
|
|
|
|
|
|
# create directory if not exists
|
|
|
|
if !File.exist?( location )
|
|
|
|
FileUtils.mkdir_p( location )
|
2013-06-12 15:59:58 +00:00
|
|
|
end
|
2014-04-28 19:20:09 +00:00
|
|
|
location += file
|
2012-04-10 14:06:46 +00:00
|
|
|
end
|
|
|
|
|
2014-04-28 17:24:47 +00:00
|
|
|
# read file from fs
|
|
|
|
def unlink_location
|
|
|
|
if File.exist?( self.get_locaton )
|
|
|
|
puts "NOTICE: storge remove '#{self.get_locaton}'"
|
|
|
|
File.delete( self.get_locaton )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-04-28 07:44:36 +00:00
|
|
|
# read file from fs
|
|
|
|
def read_from_fs
|
|
|
|
puts "read from fs #{self.get_locaton}"
|
|
|
|
return if !File.exist?( self.get_locaton )
|
|
|
|
data = File.open( self.get_locaton, 'rb' )
|
|
|
|
content = data.read
|
|
|
|
|
|
|
|
# check md5
|
|
|
|
md5 = Digest::MD5.hexdigest( content )
|
|
|
|
if md5 != self.md5
|
|
|
|
raise "ERROR: Corrupt file in fs #{self.get_locaton}, md5 should be #{self.md5} but is #{md5}"
|
|
|
|
end
|
|
|
|
content
|
|
|
|
end
|
|
|
|
|
|
|
|
# write file to fs
|
|
|
|
def write_to_fs
|
|
|
|
|
|
|
|
# install file
|
|
|
|
permission = '600'
|
|
|
|
if !File.exist?( self.get_locaton )
|
|
|
|
puts "NOTICE: storge write '#{self.get_locaton}' (#{permission})"
|
|
|
|
file = File.new( self.get_locaton, 'wb' )
|
|
|
|
file.write( self.data )
|
|
|
|
file.close
|
|
|
|
end
|
|
|
|
File.chmod( permission.to_i(8), self.get_locaton )
|
|
|
|
|
|
|
|
# check md5
|
|
|
|
md5 = Digest::MD5.hexdigest( self.read_from_fs )
|
|
|
|
if md5 != self.md5
|
|
|
|
raise "ERROR: Corrupt file in fs #{self.get_locaton}, md5 should be #{self.md5} but is #{md5}"
|
|
|
|
end
|
|
|
|
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
# write file to db
|
|
|
|
def write_to_db
|
|
|
|
|
|
|
|
# read and check md5
|
|
|
|
content = self.read_from_fs
|
|
|
|
|
|
|
|
# store in database
|
|
|
|
self.data = content
|
|
|
|
self.save
|
|
|
|
|
|
|
|
# check md5 against db content
|
|
|
|
md5 = Digest::MD5.hexdigest( self.data )
|
|
|
|
if md5 != self.md5
|
|
|
|
raise "ERROR: Corrupt file in db #{self.get_locaton}, md5 should be #{self.md5} but is #{md5}"
|
|
|
|
end
|
|
|
|
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2014-04-28 19:20:09 +00:00
|
|
|
# check database data and md5, in case fix it
|
|
|
|
def self.db_check_md5(fix_it = nil)
|
|
|
|
Store::File.where( :file_system => false ).each {|item|
|
|
|
|
md5 = Digest::MD5.hexdigest( item.data )
|
|
|
|
if md5 != item.md5
|
|
|
|
puts "DIFF: md5 diff of Store::File.find(#{item.id}) "
|
|
|
|
if fix_it
|
|
|
|
item.update_attribute( :md5, md5 )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
}
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2014-05-02 09:03:06 +00:00
|
|
|
def self.fs_check_md5(fix_it = nil)
|
|
|
|
Store::File.where( :file_system => true ).each {|item|
|
|
|
|
md5 = Digest::MD5.hexdigest( item.read_from_fs )
|
|
|
|
if md5 != item.md5
|
|
|
|
puts "DIFF: md5 diff of Store::File.find(#{item.id}) "
|
|
|
|
if fix_it
|
|
|
|
item.update_attribute( :md5, md5 )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
}
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2014-04-28 07:44:36 +00:00
|
|
|
def self.move_to_fs
|
|
|
|
Store::File.where( :file_system => false ).each {|item|
|
|
|
|
item.write_to_fs
|
|
|
|
item.update_attribute( :file_system, true )
|
|
|
|
item.update_attribute( :data, nil )
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.move_to_db
|
|
|
|
Store::File.where( :file_system => true ).each {|item|
|
|
|
|
item.write_to_db
|
|
|
|
item.update_attribute( :file_system, false )
|
2014-04-28 17:24:47 +00:00
|
|
|
item.unlink_location
|
2014-04-28 07:44:36 +00:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def check_location
|
|
|
|
|
|
|
|
# write initial to fs if needed
|
|
|
|
if self.file_system && self.data
|
|
|
|
self.write_to_fs
|
|
|
|
self.data = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def add_md5
|
|
|
|
if self.data && !self.md5
|
|
|
|
self.md5 = Digest::MD5.hexdigest( self.data )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|