mirror of
https://github.com/mistergibson/gxg-framework.git
synced 2026-08-15 03:46:00 -07:00
2820 lines
120 KiB
Ruby
2820 lines
120 KiB
Ruby
require 'pathname'
|
|
|
|
module GxG
|
|
module Storage
|
|
class BufferedSegments < ::StringIO
|
|
#
|
|
private
|
|
def refresh_map()
|
|
total = 0
|
|
position = 0
|
|
@map.each do |record|
|
|
if record[:size] > 0
|
|
record[:offset_range] = ((position)..(record[:size] - 1))
|
|
position += (record[:size] - 1)
|
|
total += record[:size]
|
|
else
|
|
record[:offset_range] = ((position)..(position))
|
|
end
|
|
end
|
|
@total_length = total
|
|
end
|
|
#
|
|
public
|
|
def initialize(file_segments=nil)
|
|
#
|
|
unless file_segments.is_a?(::GxG::Database::PersistedArray)
|
|
raise ArgumentError, "You MUST provide a PersistedArray"
|
|
end
|
|
@read_buffer = ::GxG::ByteArray.new
|
|
@write_buffer = ::GxG::ByteArray.new
|
|
@data = file_segments
|
|
@map = @data.element_byte_map(::GxG::ByteArray)
|
|
@position = 0
|
|
@total_length = 0
|
|
refresh_map()
|
|
super("")
|
|
self
|
|
end
|
|
#
|
|
def read(length = nil, outbuf = nil)
|
|
eof = false
|
|
if outbuf.is_a?(::String)
|
|
result = outbuf
|
|
else
|
|
result = ::GxG::ByteArray.new
|
|
end
|
|
#
|
|
if length.is_a?(::Integer)
|
|
if length > 0
|
|
if @total_length == 0
|
|
eof = true
|
|
else
|
|
start_offset = @position
|
|
if length > @total_length
|
|
end_offset = (start_offset + (@total_length - 1))
|
|
else
|
|
end_offset = (start_offset + (length - 1))
|
|
end
|
|
@read_buffer.clear
|
|
#
|
|
@map.each do |record|
|
|
if start_offset < record[:offset_range].first && start_offset < record[:offset_range].last
|
|
segment_start = 0
|
|
end
|
|
if start_offset >= record[:offset_range].first && start_offset <= record[:offset_range].last
|
|
segment_start = start_offset - record[:offset_range].first
|
|
end
|
|
if end_offset <= record[:offset_range].last && end_offset >= record[:offset_range].first
|
|
segment_end = (record[:size] - 1) - (record[:offset_range].last - end_offset)
|
|
end
|
|
if end_offset > record[:offset_range].last
|
|
segment_end = record[:size] - 1
|
|
end
|
|
@read_buffer << @data[(record[:index])][((segment_start)..(segment_end))].to_s
|
|
end
|
|
#
|
|
@position += (@read_buffer.size)
|
|
result << @read_buffer.to_s
|
|
@read_buffer.clear
|
|
end
|
|
end
|
|
else
|
|
# return entire data length bytes
|
|
@read_buffer.clear
|
|
#
|
|
@map.each do |record|
|
|
@read_buffer << @data[(record[:index])].to_s
|
|
end
|
|
@position += (@read_buffer.size)
|
|
result << @read_buffer.to_s
|
|
@read_buffer.clear
|
|
end
|
|
#
|
|
if eof == true
|
|
nil
|
|
else
|
|
result.to_s
|
|
end
|
|
end
|
|
#
|
|
def seek(amount=nil, whence=::IO::SEEK_SET)
|
|
unless amount.is_a?(::Integer)
|
|
amount = 0
|
|
end
|
|
unless whence
|
|
whence = ::IO::SEEK_SET
|
|
end
|
|
case whence
|
|
when ::IO::SEEK_CUR
|
|
@position += amount
|
|
when ::IO::SEEK_END
|
|
@position = @total_length + amount
|
|
when ::IO::SEEK_SET
|
|
@position = amount
|
|
end
|
|
0
|
|
end
|
|
#
|
|
# write(string, ...) → integer
|
|
# syswrite(string) → integer
|
|
# Appends the given string to the underlying buffer string. The stream must be opened for writing. If the argument is not a string, it will be converted to a string using to_s.
|
|
# Returns the number of bytes written. See IO#write.
|
|
def write(data=nil)
|
|
result = 0
|
|
@write_buffer.clear
|
|
@write_buffer << data.to_s
|
|
start_offset = @position
|
|
end_offset = start_offset + @write_buffer.size - 1
|
|
if start_offset > (@total_length - 1)
|
|
@data << @write_buffer.clone
|
|
@position += @write_buffer.size
|
|
result = @write_buffer.size
|
|
@write_buffer.clear
|
|
refresh_map()
|
|
else
|
|
manifest = []
|
|
cursor = 0
|
|
@map.each_with_index do |record|
|
|
if start_offset >= record[:offset_range].first && end_offset <= record[:offset_range].last
|
|
# AT or Within
|
|
src_start = cursor
|
|
src_end = cursor + (end_offset - start_offset)
|
|
dst_start = (start_offset - record[:offset_range].first)
|
|
dst_end = dst_start + (src_end - src_start)
|
|
manifest << {:op => :overwrite, :index => record[:index], :source_range => ((src_start)..(src_end)), :destination_range => ((dst_start)..(dst_end))}
|
|
cursor += ((src_start)..(src_end)).size
|
|
end
|
|
if start_offset < record[:offset_range].first && end_offset >= record[:offset_range].first && end_offset <= record[:offset_range].last
|
|
# Stradles Start
|
|
src_start = cursor
|
|
src_end = cursor + (end_offset - record[:offset_range].first)
|
|
dst_start = 0
|
|
dst_end = (end_offset - record[:offset_range].first)
|
|
manifest << {:op => :overwrite, :index => record[:index], :source_range => ((src_start)..(src_end)), :destination_range => ((dst_start)..(dst_end))}
|
|
cursor += ((src_start)..(src_end)).size
|
|
end
|
|
if start_offset >= record[:offset_range].first && end_offset >= record[:offset_range].last
|
|
# Strandles End
|
|
src_start = cursor
|
|
src_end = cursor + (record[:offset_range].last - start_offset)
|
|
dst_start = (start_offset - record[:offset_range].first)
|
|
dst_end = dst_start + (src_end - src_start)
|
|
manifest << {:op => :overwrite, :index => record[:index], :source_range => ((src_start)..(src_end)), :destination_range => ((dst_start)..(dst_end))}
|
|
cursor += ((src_start)..(src_end)).size
|
|
end
|
|
end
|
|
if end_offset > (@total_length - 1)
|
|
src_start = cursor
|
|
src_end = ((@write_buffer.size - 1) - cursor)
|
|
@position += (end_offset - (@total_length - 1))
|
|
manifest << {:op => :append, :source_range => ((src_start)..(src_end))}
|
|
else
|
|
@position += (end_offset + 1)
|
|
end
|
|
# Review : add truncate functionality to this method. (research)
|
|
manifest.each do |record|
|
|
if record[:op] == :overwrite
|
|
segment = @data[(record[:index])]
|
|
@write_buffer[(record[:source_range])].each_with_index do |the_byte, indexer|
|
|
segment[(indexer + record[:destination_range].first)] = the_byte
|
|
end
|
|
end
|
|
if record[:op] == :append
|
|
@data << @write_buffer[(record[:source_range])]
|
|
end
|
|
end
|
|
refresh_map()
|
|
#
|
|
end
|
|
result
|
|
end
|
|
alias :syswrite :write
|
|
#
|
|
def rewind()
|
|
@position = 0
|
|
0
|
|
end
|
|
#
|
|
def size()
|
|
@total_length
|
|
end
|
|
#
|
|
end
|
|
#
|
|
class FileSpace
|
|
def initialize()
|
|
@thread_safety = ::Mutex.new
|
|
@mounted = []
|
|
end
|
|
#
|
|
def subpath(src_path="", the_path="")
|
|
if src_path == "/"
|
|
the_path
|
|
else
|
|
src_path.each_char do |the_char|
|
|
if the_char == the_path[0]
|
|
the_path = the_path[(1..-1)]
|
|
end
|
|
end
|
|
the_path
|
|
end
|
|
end
|
|
#
|
|
def path_prefix(src_path="", the_path="")
|
|
result = ""
|
|
src_path.each_char do |the_char|
|
|
if the_char == the_path[0]
|
|
the_path = the_path[(1..-1)]
|
|
result << the_char
|
|
else
|
|
break
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def volume_of_path(the_path="")
|
|
result = nil
|
|
@thread_safety.synchronize {
|
|
@mounted.reverse.each do |volume_entry|
|
|
if self.path_prefix(volume_entry[:path].to_s,the_path.to_s) == (volume_entry[:path].to_s)
|
|
result = {:volume => volume_entry[:volume], :subpath => self.subpath(volume_entry[:path].to_s, the_path.to_s) }
|
|
break
|
|
end
|
|
end
|
|
}
|
|
result
|
|
end
|
|
#
|
|
def valid_path?(the_path="")
|
|
result = false
|
|
if the_path.to_s == "/"
|
|
result = true
|
|
else
|
|
# VFS check
|
|
path_table = []
|
|
@thread_safety.synchronize {
|
|
@mounted.reverse.each do |volume_entry|
|
|
path_entry = volume_entry[:path].to_s.split("/")
|
|
unless path_table.include?(path_entry)
|
|
path_table << path_entry
|
|
end
|
|
end
|
|
}
|
|
path_array = the_path.to_s.split("/")
|
|
vfs_check = false
|
|
path_table.each do |entry|
|
|
(0..(path_array.size - 1)).each do |indexer|
|
|
if entry == (path_array[(0..(indexer))])
|
|
vfs_check = true
|
|
break
|
|
end
|
|
end
|
|
if vfs_check
|
|
break
|
|
end
|
|
end
|
|
# volume check
|
|
if vfs_check
|
|
volume_record = self.volume_of_path(the_path.to_s)
|
|
if volume_record
|
|
result = volume_record[:volume].exist?(volume_record[:subpath].to_s)
|
|
else
|
|
result = true
|
|
end
|
|
end
|
|
#
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def exist?(the_path="")
|
|
self.valid_path?(the_path)
|
|
end
|
|
#
|
|
def get_permissions(the_path="", the_credential=nil)
|
|
result = []
|
|
if self.valid_path?(the_path.to_s)
|
|
if the_path.to_s == "/"
|
|
volume_record = nil
|
|
else
|
|
volume_record = self.volume_of_path(the_path.to_s)
|
|
end
|
|
if volume_record
|
|
result = volume_record[:volume].get_permissions(volume_record[:subpath].to_s, the_credential)
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def revoke_permissions(the_path="", the_credential=nil)
|
|
result = false
|
|
if self.valid_path?(the_path.to_s)
|
|
if the_path.to_s == "/"
|
|
volume_record = nil
|
|
else
|
|
volume_record = self.volume_of_path(the_path.to_s)
|
|
end
|
|
if volume_record
|
|
result = volume_record[:volume].revoke_permissions(volume_record[:subpath].to_s, the_credential)
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
#
|
|
def set_permissions(the_path="", the_credential=nil, the_permissions={})
|
|
result = false
|
|
if self.valid_path?(the_path.to_s)
|
|
if the_path.to_s == "/"
|
|
volume_record = nil
|
|
else
|
|
volume_record = self.volume_of_path(the_path.to_s)
|
|
end
|
|
if volume_record
|
|
result = volume_record[:volume].set_permissions(volume_record[:subpath].to_s, the_credential, the_permissions)
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def profile(the_path="",params=nil)
|
|
result = nil
|
|
if self.valid_path?(the_path.to_s)
|
|
if the_path.to_s == "/"
|
|
volume_record = nil
|
|
else
|
|
volume_record = self.volume_of_path(the_path.to_s)
|
|
end
|
|
if volume_record
|
|
if volume_record[:subpath].to_s.size > 0
|
|
if params
|
|
result = volume_record[:volume].profile(volume_record[:subpath].to_s,params)
|
|
else
|
|
result = volume_record[:volume].profile(volume_record[:subpath].to_s)
|
|
end
|
|
result[:title] = (the_path.split("/").last.to_s)
|
|
else
|
|
# VFS
|
|
if params
|
|
base_profile = volume_record[:volume].profile(volume_record[:subpath].to_s,params)
|
|
else
|
|
base_profile = volume_record[:volume].profile(volume_record[:subpath].to_s)
|
|
end
|
|
if base_profile
|
|
result = {:title => "", :type => :virtual_directory, :owner_type => :virtual_directory, :uuid => nil, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => {:effective => {:execute => true, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read=>true}}, :mode=>nil}
|
|
result[:title] = (the_path.split("/").last.to_s)
|
|
result[:accessed] = DateTime.now
|
|
if base_profile[:permissions][:effective][:write]
|
|
result[:permissions][:effective][:write] = true
|
|
result[:permissions][:effective][:create] = true
|
|
end
|
|
end
|
|
end
|
|
else
|
|
# VFS Other
|
|
result = {:title => "", :type => :virtual_directory, :owner_type => :virtual_directory, :uuid => nil, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => {:effective => {:execute => true, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read=>true}}, :mode=>nil}
|
|
result[:title] = (the_path.split("/").last.to_s)
|
|
result[:accessed] = DateTime.now
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "Path not found: #{the_path}"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path}})
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def entries(the_path="", as_credential=nil)
|
|
result = []
|
|
if self.valid_path?(the_path.to_s)
|
|
if the_path.to_s == "/"
|
|
volume_record = nil
|
|
else
|
|
volume_record = self.volume_of_path(the_path.to_s)
|
|
end
|
|
#
|
|
if volume_record
|
|
result = volume_record[:volume].entries(volume_record[:subpath].to_s, as_credential)
|
|
@mounted.each do |volume_entry|
|
|
if (volume_entry[:path].to_s).include?(the_path) && volume_entry[:path].to_s != the_path
|
|
result << self.profile(volume_entry[:path].to_s)
|
|
end
|
|
end
|
|
result = result.sort {|a,b| a[:title] <=> b[:title]}
|
|
else
|
|
path_table = []
|
|
@thread_safety.synchronize {
|
|
@mounted.reverse.each do |volume_entry|
|
|
path_entry = volume_entry[:path].to_s.split("/")
|
|
unless path_table.include?(path_entry)
|
|
path_table << path_entry
|
|
end
|
|
end
|
|
}
|
|
if the_path == "/"
|
|
offset = 1
|
|
else
|
|
offset = (the_path.to_s.split("/").size - 1)
|
|
end
|
|
#
|
|
already = []
|
|
path_table.each do |subpath|
|
|
if self.path_prefix(the_path.to_s,subpath.join("/").to_s) == the_path.to_s
|
|
if subpath[(offset)].to_s.size > 0
|
|
unless already.include?(subpath[(offset)].to_s)
|
|
the_profile = self.profile((the_path + "/" + subpath[(offset)].to_s).gsub("//","/"), {:with_credential => as_credential})
|
|
if the_profile
|
|
the_profile[:title] = (subpath[(offset)].to_s)
|
|
the_profile[:accessed] = DateTime.now
|
|
result << the_profile
|
|
end
|
|
already << subpath[(offset)].to_s
|
|
end
|
|
end
|
|
end
|
|
end
|
|
result = result.sort {|a,b| a[:title] <=> b[:title]}
|
|
#
|
|
end
|
|
#
|
|
else
|
|
begin
|
|
raise Exception, "Path not found: #{the_path}"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path}})
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def rmdir(the_path="")
|
|
result = false
|
|
if self.valid_path?(the_path.to_s)
|
|
begin
|
|
if the_path.to_s == "/"
|
|
raise Exception, "You do not have destroy permissions here"
|
|
else
|
|
volume_record = self.volume_of_path(the_path.to_s)
|
|
end
|
|
if volume_record
|
|
the_profile = self.profile(the_path.to_s)
|
|
if the_profile
|
|
if the_profile[:permissions][:effective][:destroy]
|
|
if [:virtual_directory, :directory, :persisted_array].include?(the_profile[:type])
|
|
result = volume_record[:volume].rmdir(volume_record[:subpath].to_s)
|
|
else
|
|
raise Exception, "Not a directory"
|
|
end
|
|
else
|
|
raise Exception, "You do not have destroy permissions here. See unmount method."
|
|
end
|
|
else
|
|
raise Exception, "Invalid path"
|
|
end
|
|
#
|
|
else
|
|
raise Exception, "Invalid path"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path}})
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "Invalid path"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path}})
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def rmfile(the_path="")
|
|
result = false
|
|
if self.valid_path?(the_path.to_s)
|
|
begin
|
|
if the_path.to_s == "/"
|
|
raise Exception, "You do not have destroy permissions here"
|
|
else
|
|
volume_record = self.volume_of_path(the_path.to_s)
|
|
end
|
|
if volume_record
|
|
the_profile = self.profile(the_path.to_s)
|
|
if the_profile
|
|
if the_profile[:permissions][:effective][:destroy]
|
|
if [:virtual_directory, :directory, :persisted_array].include?(the_profile[:type])
|
|
raise Exception, "Not a valid :file or :persisted_hash"
|
|
else
|
|
result = volume_record[:volume].rmfile(volume_record[:subpath].to_s)
|
|
end
|
|
else
|
|
raise Exception, "You do not have destroy permissions here. See unmount method."
|
|
end
|
|
else
|
|
raise Exception, "Invalid path"
|
|
end
|
|
#
|
|
else
|
|
raise Exception, "Invalid path"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path}})
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "Invalid path"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path}})
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def mkdir(the_path="", permissions=nil)
|
|
result = false
|
|
if self.valid_path?(the_path.to_s)
|
|
result = true
|
|
else
|
|
if self.valid_path?(::File::dirname(the_path.to_s))
|
|
if the_path.to_s == "/"
|
|
volume_record = nil
|
|
else
|
|
volume_record = self.volume_of_path(the_path.to_s)
|
|
end
|
|
if volume_record
|
|
result = volume_record[:volume].mkdir(volume_record[:subpath].to_s, permissions)
|
|
else
|
|
begin
|
|
raise Exception, "You do not have sufficient priviledges to create here. See the mount method."
|
|
rescue Exception => the_error
|
|
result = false
|
|
log_error({:error => the_error, :parameters => {:path => the_path}})
|
|
end
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "Invalid path to new directory"
|
|
rescue Exception => the_error
|
|
result = false
|
|
log_error({:error => the_error, :parameters => {:path => the_path}})
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def mkpath(the_path="")
|
|
result = false
|
|
if the_path.to_s == ""
|
|
the_path = "/"
|
|
end
|
|
if self.valid_path?(the_path.to_s)
|
|
result = true
|
|
else
|
|
error_flag = false
|
|
path_array = the_path.to_s.split("/")
|
|
(0..(path_array.size - 1)).each do |indexer|
|
|
temp_path = path_array[(0..(indexer))].join("/")
|
|
if temp_path == ""
|
|
temp_path = "/"
|
|
end
|
|
unless self.valid_path?(temp_path)
|
|
unless self.mkdir(temp_path)
|
|
error_flag = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
unless error_flag
|
|
result = true
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def open(the_path="", options={})
|
|
result = nil
|
|
if self.valid_path?(the_path.to_s)
|
|
if the_path.to_s == "/"
|
|
volume_record = nil
|
|
else
|
|
volume_record = self.volume_of_path(the_path.to_s)
|
|
end
|
|
if volume_record
|
|
result = volume_record[:volume].open(volume_record[:subpath].to_s, options)
|
|
else
|
|
begin
|
|
raise Exception, "You do not have sufficient priviledges to open here."
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
|
|
end
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "Invalid path"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def rename(the_path="", new_name="", options={})
|
|
result = false
|
|
if self.valid_path?(the_path.to_s)
|
|
if the_path.to_s == "/"
|
|
volume_record = nil
|
|
else
|
|
volume_record = self.volume_of_path(the_path.to_s)
|
|
end
|
|
if volume_record
|
|
result = volume_record[:volume].rename(volume_record[:subpath].to_s, new_name, options)
|
|
else
|
|
begin
|
|
raise Exception, "You do not have sufficient priviledges to rename here."
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
|
|
end
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "Invalid path"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def mkfile(the_path="", options={})
|
|
result = nil
|
|
if self.valid_path?(the_path.to_s)
|
|
result = self.open(the_path,options)
|
|
else
|
|
if self.valid_path?(::File::dirname(the_path.to_s))
|
|
if the_path.to_s == "/"
|
|
volume_record = nil
|
|
else
|
|
volume_record = self.volume_of_path(the_path.to_s)
|
|
end
|
|
if volume_record
|
|
result = volume_record[:volume].open(volume_record[:subpath].to_s, options)
|
|
else
|
|
begin
|
|
raise Exception, "You do not have sufficient priviledges to create here."
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
|
|
end
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "Invalid path to new :file"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
# Copying support:
|
|
def copy(source_path=nil,destination_path=nil, options={})
|
|
# Four scenarios: FS_to_FS, DB_to_DB, FS_to_DB, DB_to_FS
|
|
# Objective: handle both file/object and directory/persisted_array in one method!
|
|
# Does OVERWRITE copy onto existing files/folders of same name.
|
|
result = false
|
|
error = false
|
|
if source_path == destination_path
|
|
error = true
|
|
end
|
|
#
|
|
source = {:enclosure => nil, :profile => nil, :record => nil, :permissions => nil}
|
|
if self.valid_path?(source_path.to_s)
|
|
if source_path.to_s == "/"
|
|
source[:record] = nil
|
|
else
|
|
source[:record] = self.volume_of_path(source_path.to_s)
|
|
end
|
|
if source[:record]
|
|
source[:profile] = self.profile(source_path,options.merge({:follow_symlinks => true}))
|
|
source[:enclosure] = self.profile(File.dirname(source_path),options.merge({:follow_symlinks => true}))
|
|
source[:permissions] = self.get_permissions(source_path)
|
|
else
|
|
error = true
|
|
log_error({:error => Exception.new("You do not have sufficient priviledges."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
end
|
|
else
|
|
error = true
|
|
log_error({:error => Exception.new("Invalid source path"), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
end
|
|
# Note: if destination is not full path, but path to its future enclosure, append the filename to fully qualify the destination_path
|
|
if File.basename(source_path) != File.basename(destination_path)
|
|
destination_path = File.expand_path(destination_path + "/" + File.basename(source_path))
|
|
end
|
|
#
|
|
destination = {:enclosure => self.profile(File.dirname(destination_path),options.merge({:follow_symlinks => true})), :profile => nil, :record => nil, :permissions => source[:permissions].clone}
|
|
if self.valid_path?(destination_path.to_s)
|
|
if destination_path.to_s == "/"
|
|
destination[:record] = nil
|
|
else
|
|
destination[:record] = self.volume_of_path(destination_path.to_s)
|
|
end
|
|
if destination[:record]
|
|
destination[:profile] = self.profile(destination_path,options.merge({:follow_symlinks => true}))
|
|
else
|
|
error = true
|
|
log_error({:error => Exception.new("You do not have sufficient permissions."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
end
|
|
else
|
|
if destination[:enclosure].is_a?(::Hash)
|
|
unless destination[:enclosure][:permissions][:effective][:create] == true
|
|
error = true
|
|
log_error({:error => Exception.new("You do not have sufficient permissions to create at destination."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
end
|
|
unless error == true
|
|
destination[:record] = self.volume_of_path(File.dirname(destination_path))
|
|
if destination[:record].is_a?(::Hash)
|
|
# Note: this won't exist yet, but is here for future reference.
|
|
destination[:record][:subpath] = (destination[:record][:subpath] + "/" + File.basename(destination_path))
|
|
end
|
|
end
|
|
else
|
|
error = true
|
|
log_error({:error => Exception.new("Invalid destination path"), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
end
|
|
end
|
|
unless error == true
|
|
if source[:profile]
|
|
# determine permissions
|
|
if source[:profile][:permissions][:effective][:read] == true
|
|
# Does destination already exist? --> overwrite permissions?
|
|
if destination[:profile]
|
|
unless destination[:profile][:permissions][:effective][:write] == true
|
|
error = true
|
|
log_error({:error => Exception.new("You do not have sufficient permissions at destination."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
end
|
|
end
|
|
unless error == true
|
|
# determine copying scenario: FS_to_FS, DB_to_DB, FS_to_DB, DB_to_FS
|
|
if source[:record][:volume].file_system?()
|
|
if destination[:enclosure][:uuid].to_s.size > 0
|
|
scenario = :FS_to_DB
|
|
else
|
|
scenario = :FS_to_FS
|
|
end
|
|
else
|
|
if destination[:enclosure][:uuid].to_s.size > 0
|
|
scenario = :DB_to_DB
|
|
else
|
|
scenario = :DB_to_FS
|
|
end
|
|
end
|
|
# create source/destination path pairings:
|
|
file_list = []
|
|
dir_list = []
|
|
if ["persisted_array","directory","virtual_directory"].include?(source[:profile][:type].to_s)
|
|
# directory / collection
|
|
search_queue = [{:source => (source_path), :destination => (destination_path)}]
|
|
while search_queue.size > 0 do
|
|
entry = search_queue.shift
|
|
if entry
|
|
self.entries(entry[:source]).each do |the_profile|
|
|
new_record = {:source => (entry[:source] + "/" + the_profile[:title]), :destination => (entry[:destination] + "/" + the_profile[:title]), :mode => the_profile[:mode], :mime => the_profile[:mime]}
|
|
if ["persisted_array","directory","virtual_directory"].include?(the_profile[:type].to_s)
|
|
dir_list << new_record
|
|
search_queue << new_record
|
|
else
|
|
file_list << new_record
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
# single object
|
|
file_list << {:source => (source_path), :destination => (destination_path)}
|
|
end
|
|
# lay down dir structure
|
|
dir_list.each do |the_record|
|
|
self.mkpath(the_record[:destination])
|
|
end
|
|
#
|
|
case scenario
|
|
when :FS_to_FS
|
|
# copy files one at a time.
|
|
file_list.each do |the_record|
|
|
if self.exist?(the_record[:destination])
|
|
self.rmfile(the_record[:destination])
|
|
end
|
|
# FIXME: find a way to preserve :mode (via REAL FS path) on new file.
|
|
begin
|
|
source_file = self.open(the_record[:source])
|
|
destination_file = self.mkfile(the_record[:destination])
|
|
if source_file && destination_file
|
|
GxG::apportioned_ranges(source_file.size,65536).each do |the_range|
|
|
source_file.seek(the_range.first)
|
|
destination_file.write(GxG::ByteArray.new(source_file.read(the_range.size)).to_s)
|
|
end
|
|
destination_file.close
|
|
source_file.close
|
|
else
|
|
raise Exception, "Error opening source or destination files."
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
error = true
|
|
break
|
|
end
|
|
end
|
|
unless error == true
|
|
destination[:permissions].each do |the_entry|
|
|
self.set_permissions(destination_path, the_entry[:credential], the_entry[:permissions])
|
|
end
|
|
result = true
|
|
end
|
|
when :FS_to_DB
|
|
file_list.each do |the_record|
|
|
begin
|
|
# Question: should I support an object import format from a text file ??
|
|
if File.extname(the_record[:source]) == ".gxg_export" && (the_record[:mime] == "application/octet-stream" || the_record[:mime] == "application/json")
|
|
source_file = self.open(the_record[:source])
|
|
#
|
|
import_record = source_file.read()
|
|
source_file.close
|
|
if import_record.to_s.json?
|
|
import_record = ::JSON::parse(import_record,{:symbolize_names => true})
|
|
end
|
|
if import_record.is_a?(::Hash)
|
|
database = nil
|
|
if destination[:record].is_a?(::Hash)
|
|
if destination[:record][:volume].is_a?(GxG::Storage::Volume)
|
|
database = destination[:record][:volume].database()
|
|
end
|
|
end
|
|
if database
|
|
enclosure_uuid = self.profile(File.dirname(the_record[:destination]))[:uuid].to_s.to_sym
|
|
destination_uuid = import_record[:record][:uuid].to_s.to_sym
|
|
# Import Formats first
|
|
if import_record[:formats].is_a?(::Hash)
|
|
op_frame = {:operation => :merge_format, :data => []}
|
|
import_record[:formats].keys.each do |the_format_key|
|
|
op_frame[:data] << import_record[:formats][(the_format_key)]
|
|
end
|
|
if op_frame[:data].size > 0
|
|
database.synchronize_records([(op_frame)],destination[:record][:volume].credential())
|
|
end
|
|
end
|
|
#
|
|
database.synchronize_records([{:operation => :merge, :data => [(import_record[:record])]}],destination[:record][:volume].credential())
|
|
#
|
|
unless self.exist?(the_record[:destination])
|
|
the_enclosure = database.retrieve_by_uuid(enclosure_uuid, destination[:record][:volume].credential())
|
|
the_enclosure.wait_for_reservation
|
|
destination_object = database.retrieve_by_uuid(destination_uuid, destination[:record][:volume].credential())
|
|
the_enclosure << destination_object
|
|
destination_object.deactivate
|
|
the_enclosure.release_reservation
|
|
the_enclosure.deactivate
|
|
end
|
|
#
|
|
else
|
|
raise Exception, "Error aquiring destination database."
|
|
end
|
|
else
|
|
raise Exception, "Error importing source file."
|
|
end
|
|
else
|
|
# Import the FS file into the DB as a org.gxg.file object
|
|
source_file = self.open(the_record[:source])
|
|
destination_object = self.open(the_record[:destination],{:format => "org.gxg.file"})
|
|
if source_file && destination_object
|
|
destination_object.wait_for_reservation
|
|
destination_object[:mime] = the_record[:mime]
|
|
if destination_object[:file_segments].size > 0
|
|
(0..(destination_object[:file_segments].size - 1)).to_a.reverse.each do |the_segment_index|
|
|
destination_object[:file_segments].delete_at(the_segment_index).destroy
|
|
end
|
|
end
|
|
GxG::apportioned_ranges(source_file.size,65536).each do |the_range|
|
|
source_file.seek(the_range.first)
|
|
destination_object[:file_segments] << GxG::ByteArray.new(source_file.read(the_range.size))
|
|
destination_object[:file_segments].save
|
|
destination_object[:file_segments].unload(destination_object[:file_segments].size - 1)
|
|
end
|
|
destination_object.release_reservation
|
|
destination_object.deactivate
|
|
source_file.close
|
|
else
|
|
if source_file
|
|
source_file.close
|
|
raise Exception, "Error opening destination object."
|
|
else
|
|
raise Exception, "Error opening source file & destination object."
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
error = true
|
|
break
|
|
end
|
|
end
|
|
unless error == true
|
|
destination[:permissions].each do |the_entry|
|
|
self.set_permissions(destination_path, the_entry[:credential], the_entry[:permissions])
|
|
end
|
|
result = true
|
|
end
|
|
when :DB_to_FS
|
|
file_list.each do |the_record|
|
|
begin
|
|
# Unless the object format is org.gxg.file --> export as .gxg_export object (formats + record)
|
|
source_object = self.open(the_record[:source])
|
|
database = source_object.db_address[:database]
|
|
# check if it is an org.gxg.file for export
|
|
current_format = nil
|
|
if source_object.format().to_s.size > 0
|
|
the_format = database.format_load({:uuid => source_object.format().to_s.to_sym})
|
|
if the_format[:ufs].to_s == "org.gxg.file"
|
|
current_format = the_format[:ufs].to_s
|
|
end
|
|
end
|
|
if current_format.to_s == "org.gxg.file"
|
|
destination_file = self.open(the_record[:destination])
|
|
source_object[:file_segments].each_with_index do |the_segment, segment_index|
|
|
destination_file.write(the_segment.to_s)
|
|
source_object[:file_segments].unload(segment_index)
|
|
# Field no longer exists: the_segment.unload
|
|
end
|
|
source_object.release_reservation
|
|
source_object.deactivate
|
|
destination_file.close
|
|
else
|
|
# Review: replace with <db>.sync_export(<uuid-array>).to_json
|
|
format_records = {}
|
|
object_record = source_object.export
|
|
object_record.search do |item,selector,container|
|
|
if selector == :format || selector == :constraint
|
|
if item.to_s.size > 0
|
|
format_uuid = item
|
|
unless format_records[(item.to_s.to_sym)].is_a?(::Hash)
|
|
format_sample = database.format_load({:uuid => format_uuid.to_s.to_sym})
|
|
format_sample[:content] = format_sample[:content].gxg_export()
|
|
format_records[(format_uuid.to_s.to_sym)] = format_sample
|
|
end
|
|
end
|
|
end
|
|
end
|
|
buffer = GxG::ByteArray.new({:formats => format_records, :record => object_record}.to_json)
|
|
#
|
|
unless File.extname(the_record[:destination]) == ".gxg_export"
|
|
the_record[:destination] = (File.dirname(the_record[:destination]) + "/" + File.basename(the_record[:destination]) + ".gxg_export")
|
|
end
|
|
if self.exist?(the_record[:destination])
|
|
self.rmfile(the_record[:destination])
|
|
end
|
|
destination_file = self.open(the_record[:destination])
|
|
destination_file.write(buffer.to_s)
|
|
destination_file.close
|
|
end
|
|
#
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
error = true
|
|
break
|
|
end
|
|
end
|
|
unless error == true
|
|
destination[:permissions].each do |the_entry|
|
|
self.set_permissions(destination_path, the_entry[:credential], the_entry[:permissions])
|
|
end
|
|
result = true
|
|
end
|
|
when :DB_to_DB
|
|
source_database = source[:record][:volume].database()
|
|
destination_database = destination[:record][:volume].database()
|
|
if source_database && destination_database
|
|
file_list.each do |the_record|
|
|
source_object = self.open(the_record[:source])
|
|
current_format = nil
|
|
if source_object.format().to_s.size > 0
|
|
the_format = source_database.format_load({:uuid => source_object.format().to_s.to_sym})
|
|
if the_format[:ufs].to_s == "org.gxg.file"
|
|
current_format = the_format[:ufs].to_s
|
|
end
|
|
end
|
|
#
|
|
if source_database == destination_database
|
|
if current_format.to_s == "org.gxg.file"
|
|
import_record = {:formats => {}, :record => source_object.export({:clone => true, :exclude_file_segments => true})}
|
|
else
|
|
import_record = {:formats => {}, :record => source_object.export({:clone => true})}
|
|
end
|
|
else
|
|
if current_format.to_s == "org.gxg.file"
|
|
import_record = {:formats => {}, :record => source_object.export({:exclude_file_segments => true})}
|
|
else
|
|
import_record = {:formats => {}, :record => source_object.export()}
|
|
end
|
|
end
|
|
# Gather format records for the object.
|
|
import_record.search do |item,selector,container|
|
|
if selector == :format || selector == :constraint
|
|
if item.to_s.size > 0
|
|
format_uuid = item
|
|
unless format_records[(item.to_s.to_sym)].is_a?(::Hash)
|
|
format_sample = source_database.format_load({:uuid => format_uuid.to_s.to_sym})
|
|
format_sample[:content] = format_sample[:content].gxg_export()
|
|
format_records[(format_uuid.to_s.to_sym)] = format_sample
|
|
end
|
|
end
|
|
end
|
|
end
|
|
#
|
|
enclosure_uuid = self.profile(File.dirname(the_record[:destination]))[:uuid].to_s.to_sym
|
|
destination_uuid = import_record[:record][:uuid].to_s.to_sym
|
|
if source_database != destination_database
|
|
# Import Formats first
|
|
if import_record[:formats].is_a?(::Hash)
|
|
op_frame = {:operation => :merge_format, :data => []}
|
|
import_record[:formats].keys.each do |the_format_key|
|
|
op_frame[:data] << import_record[:formats][(the_format_key)]
|
|
end
|
|
if op_frame[:data].size > 0
|
|
destination_database.synchronize_records([(op_frame)],destination[:record][:volume].credential())
|
|
end
|
|
end
|
|
#
|
|
end
|
|
#
|
|
if self.exist?(the_record[:destination])
|
|
# Why? Because an object of the same NAME, but differing format/content may be present (unacceptable)
|
|
self.rmfile(the_record[:destination])
|
|
# Risky, but avoids situation where: diff.db, same.uuid, gets deleted, then imported to deleted object (doh!)
|
|
destination_database.empty_trash
|
|
end
|
|
#
|
|
destination_database.synchronize_records([{:operation => :merge, :data => [(import_record[:record])]}],destination[:record][:volume].credential())
|
|
#
|
|
unless self.exist?(the_record[:destination])
|
|
the_enclosure = destination_database.retrieve_by_uuid(enclosure_uuid, destination[:record][:volume].credential())
|
|
the_enclosure.wait_for_reservation
|
|
destination_object = destination_database.retrieve_by_uuid(destination_uuid, destination[:record][:volume].credential())
|
|
if current_format.to_s == "org.gxg.file"
|
|
# copy file segments
|
|
destination_object.wait_for_reservation
|
|
source_object[:file_segments].each_with_index do |the_segment, segment_index|
|
|
destination_object[:file_segments] << ByteArray.new(the_segment.to_s)
|
|
destination_object[:file_segments].save
|
|
destination_object[:file_segments].unload(destination_object[:file_segments].size - 1)
|
|
# destination_object[:file_segments][(destination_object[:file_segments].size - 1)].save
|
|
# destination_object[:file_segments][(destination_object[:file_segments].size - 1)].unload
|
|
source_object[:file_segments].unload(segment_index)
|
|
# Field no longer exists : the_segment.unload
|
|
end
|
|
destination_object.release_reservation
|
|
end
|
|
the_enclosure << destination_object
|
|
destination_object.deactivate
|
|
the_enclosure.release_reservation
|
|
the_enclosure.deactivate
|
|
end
|
|
#
|
|
end
|
|
unless error == true
|
|
destination[:permissions].each do |the_entry|
|
|
self.set_permissions(destination_path, the_entry[:credential], the_entry[:permissions])
|
|
end
|
|
result = true
|
|
end
|
|
else
|
|
log_error({:error => Exception.new("Error aquiring source or destination database."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
error = true
|
|
end
|
|
end
|
|
#
|
|
end
|
|
else
|
|
error = true
|
|
log_error({:error => Exception.new("You do not have sufficient permissions."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
# Moving support
|
|
def move(source_path=nil, destination_path=nil, options={})
|
|
result = false
|
|
error = false
|
|
if source_path == destination_path
|
|
error = true
|
|
end
|
|
#
|
|
source = {:enclosure => nil, :profile => nil, :record => nil}
|
|
if self.valid_path?(source_path.to_s)
|
|
if source_path.to_s == "/"
|
|
source[:record] = nil
|
|
else
|
|
source[:record] = self.volume_of_path(source_path.to_s)
|
|
end
|
|
if source[:record]
|
|
source[:profile] = self.profile(source_path,options.merge({:follow_symlinks => true}))
|
|
source[:enclosure] = self.profile(File.dirname(source_path),options.merge({:follow_symlinks => true}))
|
|
else
|
|
error = true
|
|
log_error({:error => Exception.new("You do not have sufficient priviledges."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
end
|
|
else
|
|
error = true
|
|
log_error({:error => Exception.new("Invalid source path"), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
end
|
|
# Note: if destination is not full path, but path to its future enclosure, append the filename to fully qualify the destination_path
|
|
if File.basename(source_path) != File.basename(destination_path)
|
|
destination_path = File.expand_path(destination_path + "/" + File.basename(source_path))
|
|
end
|
|
#
|
|
destination = {:enclosure => self.profile(File.dirname(destination_path),options.merge({:follow_symlinks => true})), :profile => nil, :record => nil}
|
|
if self.valid_path?(destination_path.to_s)
|
|
if destination_path.to_s == "/"
|
|
destination[:record] = nil
|
|
else
|
|
destination[:record] = self.volume_of_path(destination_path.to_s)
|
|
end
|
|
if destination[:record]
|
|
destination[:profile] = self.profile(destination_path,options.merge({:follow_symlinks => true}))
|
|
else
|
|
error = true
|
|
log_error({:error => Exception.new("You do not have sufficient permissions."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
end
|
|
else
|
|
if destination[:enclosure].is_a?(::Hash)
|
|
unless destination[:enclosure][:permissions][:effective][:create] == true
|
|
error = true
|
|
log_error({:error => Exception.new("You do not have sufficient permissions to create at destination."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
end
|
|
unless error == true
|
|
destination[:record] = self.volume_of_path(File.dirname(destination_path))
|
|
if destination[:record].is_a?(::Hash)
|
|
# Note: this won't exist yet, but is here for future reference.
|
|
destination[:record][:subpath] = (destination[:record][:subpath] + "/" + File.basename(destination_path))
|
|
end
|
|
end
|
|
else
|
|
error = true
|
|
log_error({:error => Exception.new("Invalid destination path"), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
|
|
end
|
|
end
|
|
#
|
|
unless error == true
|
|
if source[:record][:volume].file_system?()
|
|
if destination[:enclosure][:uuid].to_s.size > 0
|
|
scenario = :FS_to_DB
|
|
else
|
|
scenario = :FS_to_FS
|
|
end
|
|
else
|
|
if destination[:enclosure][:uuid].to_s.size > 0
|
|
scenario = :DB_to_DB
|
|
else
|
|
scenario = :DB_to_FS
|
|
end
|
|
end
|
|
if scenario == :DB_to_DB && (source[:record][:volume].database() == destination[:record][:volume].database())
|
|
database = source[:record][:volume].database()
|
|
source_enclosure = database.retrieve_by_uuid(source[:enclosure][:uuid].to_s.to_sym, source[:record][:volume].credential())
|
|
source_enclosure.wait_for_reservation
|
|
destination_enclosure = database.retrieve_by_uuid(destination[:enclosure][:uuid].to_s.to_sym, source[:record][:volume].credential())
|
|
destination_enclosure.wait_for_reservation
|
|
#
|
|
source_enclosure.each_index do |the_index|
|
|
if source_enclosure[(the_index)].uuid().to_s.to_sym == source[:profile][:uuid].to_s.to_sym
|
|
destination_enclosure << source_enclosure.delete_at(the_index)
|
|
break
|
|
else
|
|
source_enclosure[(the_index)].deactivate
|
|
end
|
|
end
|
|
source_enclosure.release_reservation
|
|
destination_enclosure.release_reservation
|
|
source_enclosure.deactivate
|
|
destination_enclosure.deactivate
|
|
result = true
|
|
else
|
|
# copy to destination_path
|
|
if self.copy(source_path, destination_path, options) == true
|
|
# delete source_path object
|
|
if ["directory","persisted_array"].include?(source[:profile][:type].to_s)
|
|
result = self.rmdir(source_path)
|
|
else
|
|
result = self.rmfile(source_path)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
#
|
|
result
|
|
end
|
|
#
|
|
def mount(the_volume = nil, the_path = "")
|
|
# Allow for a path to be mounted over the top of another - design choice
|
|
if the_volume.is_a?(::GxG::Storage::Volume)
|
|
@thread_safety.synchronize { @mounted << {:volume => the_volume, :path => ::Pathname.new(the_path.to_s)} }
|
|
true
|
|
else
|
|
@mounted
|
|
end
|
|
end
|
|
#
|
|
def mounted?(the_path="")
|
|
result = false
|
|
@thread_safety.synchronize {
|
|
@mounted.reverse.each do |entry|
|
|
if entry[:path] == the_path
|
|
result = true
|
|
break
|
|
end
|
|
end
|
|
}
|
|
result
|
|
end
|
|
#
|
|
def unmount(the_path="")
|
|
# Allow for a path to be mounted over the top of another - design choice
|
|
result = nil
|
|
@thread_safety.synchronize {
|
|
if @mounted.size > 0
|
|
(0..(@mounted.size - 1)).to_a.reverse.each do |indexer|
|
|
if the_path.to_s == (@mounted[(indexer)][:path].to_s)
|
|
result = @mounted[(indexer)][:volume]
|
|
@mounted.delete_at(indexer)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
}
|
|
# returns the volume object for proper closing
|
|
result
|
|
end
|
|
#
|
|
end
|
|
|
|
#
|
|
class Volume
|
|
#
|
|
def inspect()
|
|
"<Volume: #{(@database || @directory).inspect}>"
|
|
end
|
|
#
|
|
def credential()
|
|
@credential
|
|
end
|
|
#
|
|
def database()
|
|
@database
|
|
end
|
|
#
|
|
def directory()
|
|
@directory
|
|
end
|
|
#
|
|
def file_system?()
|
|
if self.directory()
|
|
true
|
|
else
|
|
false
|
|
end
|
|
end
|
|
#
|
|
def db_path(subpath="")
|
|
result = nil
|
|
unless subpath.to_s[0] == "/"
|
|
subpath = ("/" << subpath.to_s)
|
|
end
|
|
subpath_array = subpath.to_s.split("/")
|
|
if subpath_array.size == 0
|
|
subpath_array << "Volume Root"
|
|
else
|
|
subpath_array[0] = "Volume Root"
|
|
end
|
|
path_array = [{:uuid => @root_uuid, :title => "Volume Root"}]
|
|
subpath_array.each_with_index do |the_entry, indexer|
|
|
parent_uuid = path_array.last[:uuid]
|
|
#
|
|
@database.connector()[:array_elements].filter({:parent_uuid => parent_uuid.to_s}).order(:ordinal).each do |item|
|
|
if [:element_array, :element_hash].include?(item[:element].to_s.to_sym)
|
|
if item[:element].to_s.to_sym == :element_array
|
|
record = @database.connector()[:element_array].filter({:uuid => item[:element_array_uuid].to_s}).first
|
|
else
|
|
record = @database.connector()[:element_hash].filter({:uuid => item[:element_hash_uuid].to_s}).first
|
|
end
|
|
if record
|
|
if record[:title] == subpath_array[(indexer + 1)].to_s
|
|
path_array << {:uuid => record[:uuid].to_sym, :title => record[:title]}
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
#
|
|
end
|
|
if path_array.size == subpath_array.size
|
|
result = path_array
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def exist?(subpath="")
|
|
result = false
|
|
if @directory
|
|
if subpath.to_s == "" or subpath.to_s == "/"
|
|
the_path = (@directory.path)
|
|
else
|
|
unless subpath.to_s[0] == "/"
|
|
subpath = ("/" << subpath.to_s)
|
|
end
|
|
the_path = (@directory.path + subpath.to_s).gsub("//", "/")
|
|
end
|
|
result = ::File::exist?(the_path)
|
|
else
|
|
# DB object exist?
|
|
if self.db_path(subpath)
|
|
result = true
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def profile(subpath="",params={:follow_symlinks => true})
|
|
unless params.is_a?(::Hash)
|
|
params = {:follow_symlinks => true}
|
|
end
|
|
unless params[:follow_symlinks]
|
|
unless params.keys.include?(:follow_symlinks)
|
|
params[:follow_symlinks] = true
|
|
end
|
|
end
|
|
platform_details = GxG::SYSTEM.platform()
|
|
result = {:title => "", :type => :unspecified, :owner_type => :unspecified, :uuid => nil, :version => 0.0, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => {:effective => {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read=>false}}, :mode=>nil, :mime => nil}
|
|
if @directory
|
|
# return FS object profile
|
|
result[:title] = (subpath.split("/").last.to_s)
|
|
if subpath.to_s == "" or subpath.to_s == "/"
|
|
the_path = (@directory.path)
|
|
else
|
|
unless subpath.to_s[0] == "/"
|
|
subpath = ("/" << subpath.to_s)
|
|
end
|
|
the_path = (@directory.path + subpath.to_s).gsub("//", "/")
|
|
end
|
|
if params[:follow_symlinks]
|
|
raw_stat = ::File::stat(the_path.to_s)
|
|
else
|
|
raw_stat = ::File::lstat(the_path.to_s)
|
|
end
|
|
raw_mode = raw_stat.mode.to_s(base=8)
|
|
# puts "Got: #{raw_mode}"
|
|
if raw_mode.size < 6
|
|
raw_mode = ("0" << raw_mode)
|
|
end
|
|
# puts "Then Got: #{raw_mode}"
|
|
set_user_id = false
|
|
set_group_id = false
|
|
set_sticky_bit = false
|
|
if (raw_mode[2].to_i & 4) == 4
|
|
set_user_id = true
|
|
end
|
|
if (raw_mode[2].to_i & 2) == 2
|
|
set_group_id = true
|
|
end
|
|
if (raw_mode[2].to_i & 1) == 1
|
|
set_sticky_bit = true
|
|
end
|
|
raw_permission = { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }
|
|
case raw_stat.ftype.to_s.downcase.to_sym
|
|
when :link
|
|
# are .lnk files handled properly on win32 for this?
|
|
result[:type] = :symlink
|
|
when :file
|
|
the_extension = ::File::basename(the_path.to_s).split(".")[-1].to_s.downcase
|
|
if the_extension == "lnk"
|
|
result[:type] = :symlink
|
|
else
|
|
if ["so","la","a","dylib","dll","class"].include?(the_extension)
|
|
result[:type] = :library
|
|
else
|
|
if (["out","exe","misc","java","jar", "sh", "rb", "py", "pyc"].include?(the_extension) || raw_stat.executable_real?)
|
|
result[:type] = :application
|
|
else
|
|
# TODO: find a way to externally sense if a tty
|
|
result[:type] = :file
|
|
end
|
|
end
|
|
end
|
|
when :fifo
|
|
result[:type] = :fifo
|
|
when :characterspecial
|
|
result[:type] = :character_device
|
|
when :blockspecial
|
|
result[:type] = :block_device
|
|
when :socket
|
|
result[:type] = :socket
|
|
when :directory
|
|
result[:type] = :directory
|
|
when :unknown
|
|
result[:type] = :unknown
|
|
if raw_stat.pipe?
|
|
result[:type] = :pipe
|
|
end
|
|
else
|
|
if raw_stat.pipe?
|
|
result[:type] = :pipe
|
|
end
|
|
end
|
|
#
|
|
result[:owner_type] = result[:type]
|
|
result[:on_device] = raw_stat.dev.to_s(base=10).to_i
|
|
if result[:on_device]
|
|
result[:on_device_major] = raw_stat.dev_major
|
|
result[:on_device_minor] = raw_stat.dev_minor
|
|
end
|
|
result[:is_device] = raw_stat.rdev
|
|
if result[:is_device]
|
|
result[:is_device_major] = raw_stat.rdev_major
|
|
result[:is_device_minor] = raw_stat.rdev_minor
|
|
end
|
|
result[:inode] = raw_stat.ino.to_i
|
|
# ### :file_id
|
|
if ::GxG::SYSTEM.platform()[:platform] == :windows
|
|
# FIX / Review : use Fiddle to call GetFileInformationByHandle from Kernel32.dll
|
|
# See : https://docs.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information?redirectedfrom=MSDN
|
|
# See : https://ruby-doc.org/stdlib-2.5.3/libdoc/fiddle/rdoc/Fiddle.html
|
|
# FOR NOW : if you rename the file it will mess this idea up, but it's all I got for now.
|
|
result[:file_id] = Digest::MD5.hexdigest(the_path).to_s
|
|
else
|
|
result[:file_id] = "#{(result[:on_device] || 0).to_s}-#{(result[:on_device_major] || 0).to_s}-#{(result[:on_device_minor] || 0).to_s}-#{(result[:inode] || 0).to_s}"
|
|
end
|
|
result[:flags] = []
|
|
if ::File::writable_real?(the_path.to_s)
|
|
result[:flags] << :write
|
|
end
|
|
if ::File::readable_real?(the_path.to_s)
|
|
result[:flags] << :read
|
|
end
|
|
#
|
|
result[:hardlinks_to] = raw_stat.nlink.to_i
|
|
result[:user_id] = raw_stat.uid.to_i
|
|
result[:group_id] = raw_stat.gid.to_i
|
|
result[:size] = raw_stat.size
|
|
result[:block_size] = raw_stat.blksize
|
|
result[:blocks] = raw_stat.blocks
|
|
result[:version] = raw_stat.mtime.to_f.to_d
|
|
result[:accessed] = DateTime::parse(raw_stat.atime.to_s)
|
|
result[:modified] = DateTime::parse(raw_stat.mtime.to_s)
|
|
result[:status_modified] = DateTime::parse(raw_stat.ctime.to_s)
|
|
# File permissions work in an ACL style, even if based only upon Unix-style permissions.
|
|
#
|
|
result[:permissions] = {:effective => raw_permission.clone, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone}
|
|
if set_user_id
|
|
result[:flags] << :user_id_on_execute
|
|
end
|
|
if set_group_id
|
|
result[:flags] << :group_id_on_execute
|
|
end
|
|
if set_sticky_bit
|
|
result[:flags] << :sticky_bit
|
|
end
|
|
#
|
|
# owner permissions
|
|
if (raw_mode[3].to_i & 4) == 4
|
|
result[:permissions][:owner][:read] = true
|
|
end
|
|
if (raw_mode[3].to_i & 2) == 2
|
|
result[:permissions][:owner][:write] = true
|
|
if platform_details[:platform] == :windows
|
|
# :windows way of reading :rename, :move, and :destroy permission for file/directory.
|
|
else
|
|
result[:permissions][:owner][:rename] = true
|
|
result[:permissions][:owner][:move] = true
|
|
result[:permissions][:owner][:destroy] = true
|
|
result[:permissions][:owner][:create] = true
|
|
end
|
|
end
|
|
if (raw_mode[3].to_i & 1) == 1
|
|
result[:permissions][:owner][:execute] = true
|
|
end
|
|
# group permissions
|
|
if (raw_mode[4].to_i & 4) == 4
|
|
result[:permissions][:group][:read] = true
|
|
end
|
|
if (raw_mode[4].to_i & 2) == 2
|
|
result[:permissions][:group][:write] = true
|
|
if platform_details[:platform] == :windows
|
|
# :windows way of reading :rename, :move, and :destroy permission for file/directory.
|
|
else
|
|
result[:permissions][:group][:rename] = true
|
|
result[:permissions][:group][:move] = true
|
|
result[:permissions][:group][:destroy] = true
|
|
result[:permissions][:group][:create] = true
|
|
end
|
|
end
|
|
if (raw_mode[4].to_i & 1) == 1
|
|
result[:permissions][:group][:execute] = true
|
|
end
|
|
# other permissions
|
|
if (raw_mode[5].to_i & 4) == 4
|
|
result[:permissions][:other][:read] = true
|
|
end
|
|
if (raw_mode[5].to_i & 2) == 2
|
|
result[:permissions][:other][:write] = true
|
|
if platform_details[:platform] == :windows
|
|
# :windows way of reading :rename, :move, and :destroy permission for file/directory.
|
|
else
|
|
result[:permissions][:other][:rename] = true
|
|
result[:permissions][:other][:move] = true
|
|
result[:permissions][:other][:destroy] = true
|
|
result[:permissions][:other][:create] = true
|
|
end
|
|
end
|
|
if (raw_mode[5].to_i & 1) == 1
|
|
result[:permissions][:other][:execute] = true
|
|
end
|
|
# effective (this user) permissions (while opened, supercedes normal effective file system permissions)
|
|
if result[:flags].index(:read)
|
|
result[:permissions][:effective][:read] = true
|
|
#
|
|
end
|
|
# ### db-based virtualized permission system keyed by :file_id
|
|
# ### on non-linux systems - make sure to support the :file_id format subsystem, and map to local-arch-platform equivilents.
|
|
# search_database(credential, {:ufs => "the.ufs.code", :properties => [{:the_property => {:equals => value}}]})
|
|
if ::GxG::valid_uuid?(params[:with_credential] || @credential)
|
|
acts_as = :group
|
|
current_roles = GxG::DB[:authority].user_roles(params[:with_credential] || @credential)
|
|
current_roles.each do |role_record|
|
|
unless GxG::DB[:roles][:vfs].vfs_permission_exist?(result[:file_id], role_record[:credential])
|
|
if role_record[:credential] == GxG::DB[:authority][:system_credentials][:designers] || role_record[:credential] == GxG::DB[:authority][:system_credentials][:developers] || role_record[:credential] == GxG::DB[:authority][:system_credentials][:administrators]
|
|
GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], role_record[:credential], result[:permissions][:owner])
|
|
acts_as = :owner
|
|
else
|
|
GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], role_record[:credential], result[:permissions][:group])
|
|
end
|
|
#
|
|
end
|
|
end
|
|
#
|
|
unless GxG::DB[:roles][:vfs].vfs_permission_exist?(result[:file_id], :"00000000-0000-4000-0000-000000000000")
|
|
GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], :"00000000-0000-4000-0000-000000000000", result[:permissions][:other])
|
|
end
|
|
# ### Review : should I support individual permissions at all, or rather confine it to roles and the public id?
|
|
# unless GxG::DB[:roles][:vfs].vfs_permission_exist?(result[:file_id], (params[:with_credential] || @credential))
|
|
# if acts_as == :owner
|
|
# GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], (params[:with_credential] || @credential), result[:permissions][:owner])
|
|
# else
|
|
# GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], (params[:with_credential] || @credential), result[:permissions][:group])
|
|
# end
|
|
# end
|
|
#
|
|
result[:permissions][:effective] = GxG::DB[:roles][:vfs].effective_vfs_permission(result[:file_id], (params[:with_credential] || @credential))
|
|
#
|
|
else
|
|
log_error({:error => Exception.new("Invalid credential: #{params[:with_credential] || @credential}")})
|
|
end
|
|
result[:permissions].delete(:owner)
|
|
result[:permissions].delete(:group)
|
|
result[:permissions].delete(:other)
|
|
#
|
|
#
|
|
# if @act_as.is_a?(::Symbol) || params[:with_credential]
|
|
# if ::GxG::valid_uuid?(params[:with_credential])
|
|
# if GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:administrators], params[:with_credential].to_s.to_sym)
|
|
# now_act_as = :owner
|
|
# else
|
|
# if GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:developers].to_s.to_sym, params[:with_credential].to_s.to_sym) || GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:designers].to_s.to_sym, params[:with_credential].to_s.to_sym)
|
|
# now_act_as = :group
|
|
# else
|
|
# now_act_as = :other
|
|
# end
|
|
# end
|
|
# result[:permissions][:effective] = result[:permissions][(now_act_as)].clone
|
|
# else
|
|
# result[:permissions][:effective] = result[:permissions][(@act_as)].clone
|
|
# end
|
|
# else
|
|
# if result[:flags].index(:write)
|
|
# result[:permissions][:effective][:write] = true
|
|
# if platform_details[:platform] == :windows
|
|
# # :windows way of reading :rename, :move, and :destroy permission for file/directory.
|
|
# else
|
|
# result[:permissions][:effective][:rename] = true
|
|
# result[:permissions][:effective][:move] = true
|
|
# result[:permissions][:effective][:destroy] = true
|
|
# result[:permissions][:effective][:create] = true
|
|
# end
|
|
# end
|
|
# if raw_stat.executable?
|
|
# result[:permissions][:effective][:execute] = true
|
|
# end
|
|
# end
|
|
#
|
|
result[:mode] = raw_mode
|
|
# Mime type identification:
|
|
if File.directory?(the_path)
|
|
mime_type = "inode/directory"
|
|
else
|
|
mime_type = MimeMagic.by_extension(File.extname(result[:title]))
|
|
unless mime_type
|
|
mime_type = MimeMagic.by_path(the_path)
|
|
end
|
|
unless mime_type
|
|
handle = ::File.open(the_path,"rb")
|
|
handle.rewind
|
|
mime_type = ::MimeMagic.by_magic(handle)
|
|
handle.close
|
|
end
|
|
if mime_type
|
|
mime_type = mime_type.type()
|
|
else
|
|
mime_type = "application/octet-stream"
|
|
end
|
|
end
|
|
result[:mime] = mime_type
|
|
#
|
|
#
|
|
else
|
|
# return DB object profile
|
|
unless subpath.to_s[0] == "/"
|
|
subpath = ("/" << subpath.to_s)
|
|
end
|
|
#
|
|
path_array = self.db_path(subpath.to_s)
|
|
if path_array
|
|
object_details = {:uuid => path_array.last[:uuid], :table => :unspecified, :dbid => 0}
|
|
header = @database.connector()[:element_hash].filter({:uuid => object_details[:uuid].to_s}).first
|
|
if header
|
|
object_details[:table] = :element_hash
|
|
object_details[:dbid] = header[:dbid]
|
|
else
|
|
header = @database.connector()[:element_array].filter({:uuid => object_details[:uuid].to_s}).first
|
|
if header
|
|
object_details[:table] = :element_array
|
|
object_details[:dbid] = header[:dbid]
|
|
end
|
|
end
|
|
#
|
|
result = {:title => path_array.last[:title], :type => :virtual_directory, :owner_type => :virtual_directory, :uuid => path_array.last[:uuid], :version => ::BigDecimal.new(header[:version].to_s), :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :file_id => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => {:effective => {:execute => true, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read=>true}}, :mode=>nil, :mime => nil}
|
|
if object_details[:table] == :element_array
|
|
result[:type] = :persisted_array
|
|
result[:owner_type] = :persisted_array
|
|
else
|
|
result[:type] = :persisted_hash
|
|
result[:owner_type] = :persisted_hash
|
|
end
|
|
if ::GxG::valid_uuid?(params[:with_credential])
|
|
result[:permissions][:effective] = @database.effective_uuid_permission(object_details[:uuid], params[:with_credential])
|
|
else
|
|
result[:permissions][:effective] = @database.effective_uuid_permission(object_details[:uuid], @credential)
|
|
end
|
|
# Object Size in DB:
|
|
if result[:type] == :persisted_hash
|
|
# Review : DB overhaul - ensure @database.byte_size_by_uuid supports new arch.
|
|
# result[:size] = @database.byte_size_by_uuid(object_details[:uuid])
|
|
else
|
|
# result[:size] = 0
|
|
end
|
|
# Pseudo-mime for objects:
|
|
mime_type = nil
|
|
if header[:format].to_s.size > 0
|
|
the_format = @database.format_list({:uuid => header[:format]})[0]
|
|
if the_format
|
|
mime_type = "vnd.gxg/#{the_format[:ufs].to_s.gsub('_','-')}"
|
|
else
|
|
mime_type = "vnd.gxg/org.gxg.hash.persisted"
|
|
end
|
|
else
|
|
if object_details[:table] == :element_array
|
|
mime_type = "vnd.gxg/org.gxg.array.persisted"
|
|
end
|
|
end
|
|
result[:mime] = mime_type
|
|
else
|
|
# raise Exception, "Invalid path"
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def initialize(options={})
|
|
# options: :directory => "/path/to/dir", :credential => <UUID>.to_s, :act_as => :owner? || :database => <GxG::Database::Database>, :credential => <UUID>.to_s, :root_uuid => <UUID>.to_s
|
|
@thread_safety = ::Mutex.new
|
|
@credential = GxG::DB[:administrator].to_s
|
|
@database = nil
|
|
@root_uuid = nil
|
|
# Acts As: :owner, :group, :other for File System Volumes
|
|
if [:owner, :group, :other].include?(options[:act_as])
|
|
@act_as = options[:act_as]
|
|
else
|
|
@act_as = nil
|
|
end
|
|
@directory = options[:directory]
|
|
if @directory.to_s.size > 0
|
|
if ::GxG::valid_uuid?(options[:credential].to_s.to_sym)
|
|
unless @act_as
|
|
# set @act_as according to credential Group/Role membership: Administrators=:owner, Developers/Designers=:group, all others=:other
|
|
if GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:administrators], options[:credential].to_s.to_sym)
|
|
@act_as = :owner
|
|
else
|
|
if GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:developers].to_s.to_sym, options[:credential].to_s.to_sym) || GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:designers].to_s.to_sym, options[:credential].to_s.to_sym)
|
|
@act_as = :group
|
|
else
|
|
@act_as = :other
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if ::Dir.exist?(@directory.to_s)
|
|
if ::File.readable_real?(@directory.to_s)
|
|
@directory = ::Dir.new(@directory.to_s)
|
|
else
|
|
raise Exception, "You do not have read permissions to directory #{@directory.inspect}."
|
|
end
|
|
else
|
|
raise Exception, "Directory #{@directory.inspect} does not exist in the filesystem."
|
|
end
|
|
#
|
|
else
|
|
@credential = (options[:credential] || GxG::DB[:administrator]).to_s
|
|
if options[:database].is_a?(::GxG::Database::Database) and @credential.size > 0
|
|
@database = options[:database]
|
|
if @database.setting_keys.include?(:volume_information)
|
|
vol_info = @database[:volume_information]
|
|
else
|
|
if @database.db_permissions()[:write]
|
|
new_root = @database.try_persist([], @credential)
|
|
if new_root
|
|
new_root.wait_for_reservation()
|
|
new_root.set_title("Volume Root")
|
|
new_root.set_permissions(@credential, {:destroy => false, :move => false, :rename => false, :create => true, :write => true})
|
|
vol_info = {:volume_root => new_root.uuid()}
|
|
new_root.deactivate
|
|
@database[:volume_information] = vol_info
|
|
else
|
|
raise Exception, "Unable to initialize volume root"
|
|
end
|
|
else
|
|
raise Exception, "Cannot initialize Volume Information on read-only database."
|
|
end
|
|
end
|
|
# One can specify a sub-array to the volume as this volume's root array.
|
|
if GxG::valid_uuid?(options[:root_uuid].to_s)
|
|
# verify array's existence
|
|
the_persisted_array = @database.retrieve_by_uuid(options[:root_uuid], @credential)
|
|
if the_persisted_array.is_a?(::GxG::Database::PersistedArray)
|
|
the_persisted_array.deactivate
|
|
@root_uuid = options[:root_uuid].to_s.to_sym
|
|
else
|
|
raise Exception, "The root_uuid points to a non-existent or invalid object, or you do not have permissions to read it."
|
|
end
|
|
#
|
|
else
|
|
@root_uuid = vol_info[:volume_root].to_sym
|
|
end
|
|
else
|
|
raise ArgumentError, "You MUST supply a valid credential and a database object. :credential => #{@credential.inspect}, :database => #{options[:database].inspect}"
|
|
end
|
|
end
|
|
#
|
|
end
|
|
#
|
|
def entries(subpath="", as_credential=nil, options={:follow_symlinks => true})
|
|
results = []
|
|
if @directory
|
|
# FS
|
|
if subpath.to_s == "" or subpath.to_s == "/"
|
|
the_path = (@directory.path)
|
|
else
|
|
unless subpath.to_s[0] == "/"
|
|
subpath = ("/" << subpath.to_s)
|
|
end
|
|
the_path = (@directory.path + subpath.to_s).gsub("//", "/")
|
|
end
|
|
::Dir::entries(the_path).each do |item|
|
|
unless [".", ".."].include?(item)
|
|
if as_credential
|
|
the_profile = self.profile((subpath + "/" + item), options.merge({:with_credential => as_credential}))
|
|
else
|
|
the_profile = self.profile((subpath + "/" + item), options)
|
|
end
|
|
if the_profile
|
|
the_profile[:title] = item
|
|
results << the_profile
|
|
end
|
|
end
|
|
end
|
|
#
|
|
else
|
|
# DB
|
|
unless subpath.to_s[0] == "/"
|
|
subpath = ("/" << subpath.to_s)
|
|
end
|
|
path_array = self.db_path(subpath.to_s)
|
|
if path_array
|
|
base_profile = self.profile(subpath.to_s, {:with_credential => as_credential})
|
|
if base_profile
|
|
if base_profile[:permissions][:effective][:read]
|
|
if base_profile[:type] == :persisted_array
|
|
object_details = {:uuid => path_array.last[:uuid], :table => :unspecified, :dbid => 0}
|
|
header = @database.connector()[:element_array].filter({:uuid => object_details[:uuid].to_s}).first
|
|
if header
|
|
object_details[:table] = :element_array
|
|
object_details[:dbid] = header[:dbid]
|
|
end
|
|
contained_titles = []
|
|
@database.connector()[:array_elements].select(:element_hash_uuid, :element_array_uuid).where({:parent_uuid => path_array.last[:uuid].to_s}).each do |link_record|
|
|
if link_record[:element_hash_uuid].to_s.size > 0
|
|
header = @database.connector()[:element_hash].filter({:uuid => link_record[:element_hash_uuid].to_s}).first
|
|
if header
|
|
contained_titles << header[:title].to_s
|
|
end
|
|
else
|
|
if link_record[:element_array_uuid].to_s.size > 0
|
|
header = @database.connector()[:element_array].filter({:uuid => link_record[:element_array_uuid].to_s}).first
|
|
if header
|
|
contained_titles << header[:title].to_s
|
|
end
|
|
end
|
|
end
|
|
end
|
|
contained_titles.sort.each do |the_title|
|
|
if the_title.size > 0
|
|
results << self.profile((subpath.to_s + "/" + the_title).gsub("//","/"), {:with_credential => as_credential})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
results
|
|
end
|
|
#
|
|
def rmdir(subpath="")
|
|
result = false
|
|
if @directory
|
|
# FS rmdir
|
|
if subpath.to_s == "" or subpath.to_s == "/"
|
|
the_path = (@directory.path)
|
|
else
|
|
unless subpath.to_s[0] == "/"
|
|
subpath = ("/" << subpath.to_s)
|
|
end
|
|
the_path = (@directory.path + subpath.to_s).gsub("//", "/")
|
|
end
|
|
#
|
|
if ::File::exist?(the_path)
|
|
begin
|
|
the_profile = self.profile(subpath.to_s)
|
|
if the_profile
|
|
if the_profile[:permissions][:effective][:destroy]
|
|
# Recursively delete any content files and subdirs first
|
|
file_list = []
|
|
dir_list = []
|
|
vfs_list = [ (the_profile[:file_id]) ]
|
|
# Gather up all VFS-DB :file_id entries:
|
|
search_queue = [(subpath)]
|
|
while search_queue.size > 0 do
|
|
entry = search_queue.shift
|
|
self.entries(entry).each do |the_subitem_profile|
|
|
if the_subitem_profile[:file_id].to_s.size > 0
|
|
vfs_list << the_subitem_profile[:file_id]
|
|
end
|
|
if [:virtual_directory, :persisted_array, :directory].include?(the_subitem_profile[:type])
|
|
search_queue << (entry + "/" + the_subitem_profile[:title])
|
|
end
|
|
end
|
|
end
|
|
# Do the FS work:
|
|
search_queue = [(the_path)]
|
|
while search_queue.size > 0 do
|
|
entry = search_queue.shift
|
|
if entry
|
|
Dir.entries(entry).each do |the_item_name|
|
|
unless [".",".."].include?(the_item_name)
|
|
if File.directory?(entry + "/" + the_item_name)
|
|
dir_list << (entry + "/" + the_item_name)
|
|
search_queue << (entry + "/" + the_item_name)
|
|
else
|
|
file_list << (entry + "/" + the_item_name)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
file_list.each do |the_file_path|
|
|
File.delete(the_file_path)
|
|
end
|
|
dir_list.each do |the_dir_path|
|
|
::Dir::rmdir(the_dir_path)
|
|
end
|
|
if ::Dir::rmdir(the_path) == 0
|
|
vfs_list.each do |the_file_id|
|
|
GxG::DB[:roles][:vfs].destroy_vfs_permission(the_file_id)
|
|
end
|
|
result = true
|
|
end
|
|
else
|
|
raise Exception, "You do not have destroy permissions to do this"
|
|
end
|
|
else
|
|
raise Exception, "Inavalid path"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path}})
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "Invaid path"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path}})
|
|
end
|
|
end
|
|
#
|
|
else
|
|
# DB rmdir
|
|
unless subpath.to_s[0] == "/"
|
|
subpath = ("/" << subpath.to_s)
|
|
end
|
|
path_array = self.db_path(subpath.to_s)
|
|
if path_array
|
|
if path_array.size > 1
|
|
the_profile = self.profile(subpath.to_s)
|
|
if the_profile
|
|
if the_profile[:permissions][:effective][:destroy]
|
|
parent_uuid = (path_array[-2] || {})[:uuid]
|
|
link_uuids = []
|
|
found_link = nil
|
|
if parent_uuid
|
|
@database.connector()[:array_elements].filter({:parent_uuid => parent_uuid.to_s}).order(:ordinal).each do |link_record|
|
|
header = nil
|
|
if link_record[:element_array_uuid].to_s.size > 0
|
|
header = @database.connector()[:element_array].filter({:uuid => link_record[:element_array_uuid].to_s}).first
|
|
end
|
|
if header
|
|
if header[:uuid] == path_array.last[:uuid] && header[:title] == path_array.last[:title]
|
|
found_link = {:uuid => header[:uuid], :linkid => link_record[:dbid]}
|
|
else
|
|
link_uuids << {:uuid => header[:uuid], :linkid => link_record[:dbid]}
|
|
end
|
|
end
|
|
end
|
|
if found_link
|
|
# Review : how to deal with lots of users deleting objects at once??
|
|
if @database.destroy_by_uuid(@credential, found_link[:uuid].to_sym) == true
|
|
# ### Delete array_elements link of id ...
|
|
@database.connector()[:array_elements].filter({:dbid => found_link[:linkid]}).delete
|
|
# ### Reset ordinals of array_elements links. (Even though VFS.entries sorts results)
|
|
link_uuids.each_with_index do |link_record, ordinal|
|
|
@database.connector()[:array_elements].filter({:dbid => found_link[:linkid]}).update({:ordinal => ordinal})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "You do not have permissions to destroy this object"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => subpath}})
|
|
end
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "Invalid path"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => subpath}})
|
|
end
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "You do not have permissions to destroy this object"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => subpath}})
|
|
end
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "Invalid path"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => subpath}})
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def rmfile(subpath="")
|
|
result = false
|
|
if @directory
|
|
# FS rmfile
|
|
if subpath.to_s == "" or subpath.to_s == "/"
|
|
the_path = (@directory.path)
|
|
else
|
|
unless subpath.to_s[0] == "/"
|
|
subpath = ("/" << subpath.to_s)
|
|
end
|
|
the_path = (@directory.path + subpath.to_s).gsub("//", "/")
|
|
end
|
|
#
|
|
if ::File::exist?(the_path)
|
|
begin
|
|
the_profile = self.profile(subpath.to_s)
|
|
if the_profile
|
|
if the_profile[:permissions][:effective][:destroy]
|
|
if ::File::delete(the_path)
|
|
GxG::DB[:roles][:vfs].destroy_vfs_permission(the_profile[:file_id])
|
|
result = true
|
|
end
|
|
else
|
|
raise Exception, "You do not have destroy permissions to do this"
|
|
end
|
|
else
|
|
raise Exception, "Inavalid path"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path}})
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "Invaid path"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path}})
|
|
end
|
|
end
|
|
#
|
|
else
|
|
# DB rmfile
|
|
path_array = self.db_path(subpath.to_s)
|
|
if path_array
|
|
the_profile = self.profile(subpath.to_s)
|
|
if the_profile
|
|
if the_profile[:permissions][:effective][:destroy]
|
|
parent_uuid = (path_array[-2] || {})[:uuid]
|
|
link_uuids = []
|
|
found_link = nil
|
|
if parent_uuid
|
|
@database.connector()[:array_elements].filter({:parent_uuid => parent_uuid.to_s}).order(:ordinal).each do |link_record|
|
|
header = nil
|
|
if link_record[:element_hash_uuid].to_s.size > 0
|
|
header = @database.connector()[:element_hash].filter({:uuid => link_record[:element_hash_uuid].to_s}).first
|
|
end
|
|
if header
|
|
if header[:uuid] == path_array.last[:uuid] && header[:title] == path_array.last[:title]
|
|
found_link = {:uuid => header[:uuid], :linkid => link_record[:dbid]}
|
|
else
|
|
link_uuids << {:uuid => header[:uuid], :linkid => link_record[:dbid]}
|
|
end
|
|
end
|
|
end
|
|
if found_link
|
|
# Review : how to deal with lots of users deleting objects at once??
|
|
if @database.destroy_by_uuid(@credential, found_link[:uuid].to_sym) == true
|
|
# ### Delete array_elements link of id ...
|
|
@database.connector()[:array_elements].filter({:dbid => found_link[:linkid]}).delete
|
|
# ### Reset ordinals of array_elements links. (Even though VFS.entries sorts results)
|
|
link_uuids.each_with_index do |link_record, ordinal|
|
|
@database.connector()[:array_elements].filter({:dbid => found_link[:linkid]}).update({:ordinal => ordinal})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
#
|
|
else
|
|
begin
|
|
raise Exception, "You do not have destroy permissions to do this"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => subpath.to_s}})
|
|
end
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "Invaid path"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => subpath.to_s}})
|
|
end
|
|
end
|
|
else
|
|
begin
|
|
raise Exception, "Invaid path"
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => subpath.to_s}})
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def rename(subpath="", new_name="", options={})
|
|
result = false
|
|
if @directory
|
|
if subpath.to_s == "" or subpath.to_s == "/"
|
|
the_path = (@directory.path)
|
|
else
|
|
unless subpath.to_s[0] == "/"
|
|
subpath = ("/" << subpath.to_s)
|
|
end
|
|
the_path = (@directory.path + subpath.to_s).gsub("//", "/")
|
|
end
|
|
# FS
|
|
begin
|
|
if options[:with_credential]
|
|
item_profile = self.profile(subpath.to_s,options.merge({:follow_symlinks => true}))
|
|
else
|
|
item_profile = self.profile(subpath.to_s)
|
|
end
|
|
if item_profile.is_a?(::Hash)
|
|
if item_profile[:permissions][:effective][:rename] == true
|
|
::File.rename(the_path,(File.dirname(the_path) + "/" + new_name))
|
|
# Review : check for file_id changes and update the VFS-DB permission records.
|
|
result = true
|
|
else
|
|
raise Exception, "You do not have rename permissions here"
|
|
end
|
|
else
|
|
raise Exception, "Invalid path"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
|
|
#
|
|
end
|
|
else
|
|
# DB open/create :persisted_hash or open :persisted_array
|
|
begin
|
|
path_array = self.db_path(subpath.to_s)
|
|
if path_array
|
|
if options[:with_credential]
|
|
profile = self.profile(subpath.to_s, options)
|
|
else
|
|
profile = self.profile(subpath.to_s)
|
|
end
|
|
profile = self.profile(subpath.to_s)
|
|
if profile[:permissions][:effective][:rename] == true
|
|
if profile[:type] == :persisted_hash
|
|
@database.connector()[:element_hash].filter({:uuid => profile[:uuid].to_s}).update({:title => new_name})
|
|
result = true
|
|
else
|
|
if profile[:type] == :persisted_array
|
|
@database.connector()[:element_array].filter({:uuid => profile[:uuid].to_s}).update({:title => new_name})
|
|
result = true
|
|
end
|
|
end
|
|
#
|
|
else
|
|
raise Exception, "You do not have rename permissions here"
|
|
end
|
|
#
|
|
else
|
|
raise Exception, "Invalid path"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def open(subpath="", options={})
|
|
result = nil
|
|
if @directory
|
|
if subpath.to_s == "" or subpath.to_s == "/"
|
|
the_path = (@directory.path)
|
|
else
|
|
unless subpath.to_s[0] == "/"
|
|
subpath = ("/" << subpath.to_s)
|
|
end
|
|
the_path = (@directory.path + subpath.to_s).gsub("//", "/")
|
|
end
|
|
flags = 0
|
|
flag_array = (options[:flags] || [:readwrite])
|
|
flag_array.each do |the_flag|
|
|
case the_flag
|
|
when :append
|
|
flags += ::File::APPEND
|
|
when :binary
|
|
flags += ::File::BINARY
|
|
when :create
|
|
flags += ::File::CREAT
|
|
when :direct
|
|
flags += ::File::DIRECT
|
|
when :dsync
|
|
flags += ::File::DSYNC
|
|
when :duplex
|
|
flags += ::File::DUPLEX
|
|
when :exclusive
|
|
flags += ::File::EXCL
|
|
when :casefold
|
|
flags += ::File::FNM_CASEFOLD
|
|
when :dotmatch
|
|
flags += ::File::FNM_DOTMATCH
|
|
when :no_escape
|
|
flags += ::File::FNM_NOESCAPE
|
|
when :pathname
|
|
flags += ::File::FNM_PATHNAME
|
|
when :system_case
|
|
flags += ::File::FNM_SYSCASE
|
|
when :lock_ex
|
|
flags += ::File::LOCK_EX
|
|
when :lock_nb
|
|
flags += ::File::LOCK_NB
|
|
when :lock_sh
|
|
flags += ::File::LOCK_SH
|
|
when :lock_un
|
|
flags += ::File::LOCK_UN
|
|
when :no_atime
|
|
flags += ::File::NOATIME
|
|
when :no_ctty
|
|
flags += ::File::NOCTTY
|
|
when :no_follow
|
|
flags += ::File::NOFOLLOW
|
|
when :nonblock
|
|
flags += ::File::NONBLOCK
|
|
when :read
|
|
flags += ::File::RDONLY
|
|
when :rsync
|
|
flags += ::File::RSYNC
|
|
when :seek_cur
|
|
flags += ::File::SEEK_CUR
|
|
when :seek_end
|
|
flags += ::File::SEEK_END
|
|
when :seek_set
|
|
flags += ::File::SEEK_SET
|
|
when :setenc_by_bom
|
|
flags += ::File::SETENC_BY_BOM
|
|
when :sync
|
|
flags += ::File::SYNC
|
|
when :text
|
|
flags += ::File::TEXT
|
|
when :truncate
|
|
flags += ::File::TRUNC
|
|
when :tty
|
|
flags += ::File::TTY
|
|
when :write
|
|
flags += ::File::WRONLY
|
|
when :readwrite
|
|
flags += ::File::RDWR
|
|
when :wsplit
|
|
flags += ::File::WSPLIT
|
|
when :wsplit_initialized
|
|
flags += ::File::WSPLIT_INITIALIZED
|
|
end
|
|
end
|
|
perm = 16384
|
|
permissions = options[:permissions]
|
|
if permissions.is_a?(::Hash)
|
|
# Best if you pass the entire :permissions hash from a profile query
|
|
"040000".each_char_with_index do |digit, indexer|
|
|
case indexer
|
|
when 0
|
|
when 1
|
|
# TODO: process other flags
|
|
when 2
|
|
when 3
|
|
if permissions[:owner].is_a?(::Hash)
|
|
if permissions[:owner][:execute]
|
|
perm += 256
|
|
end
|
|
if permissions[:owner][:write]
|
|
perm += 128
|
|
end
|
|
if permissions[:owner][:read]
|
|
perm += 64
|
|
end
|
|
end
|
|
when 4
|
|
if permissions[:group].is_a?(::Hash)
|
|
if permissions[:group][:execute]
|
|
perm += 32
|
|
end
|
|
if permissions[:group][:write]
|
|
perm += 16
|
|
end
|
|
if permissions[:group][:read]
|
|
perm += 8
|
|
end
|
|
end
|
|
when 5
|
|
if permissions[:other].is_a?(::Hash)
|
|
if permissions[:other][:execute]
|
|
perm += 4
|
|
end
|
|
if permissions[:other][:write]
|
|
perm += 2
|
|
end
|
|
if permissions[:other][:read]
|
|
perm += 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
#
|
|
else
|
|
base_profile = self.profile("/")
|
|
if base_profile.is_a?(::Hash)
|
|
if base_profile[:mode].is_a?(::String)
|
|
perm = (base_profile[:mode][0..2].to_s + "644").to_i(base=8)
|
|
else
|
|
perm = 16804
|
|
end
|
|
else
|
|
perm = 16804
|
|
end
|
|
end
|
|
# FS
|
|
begin
|
|
parent_profile = self.profile(::File::dirname(subpath.to_s))
|
|
if parent_profile
|
|
if ::File::exist?(the_path)
|
|
if ::File::directory?(the_path)
|
|
result = ::Dir::new(the_path)
|
|
else
|
|
if flag_array.include?(:read)
|
|
unless parent_profile[:permissions][:effective][:read]
|
|
raise Exception, "You do not have read permissions here."
|
|
end
|
|
end
|
|
if flag_array.include?(:write)
|
|
unless parent_profile[:permissions][:effective][:write]
|
|
raise Exception, "You do not have write permissions here."
|
|
end
|
|
end
|
|
if flag_array.include?(:readwrite)
|
|
unless parent_profile[:permissions][:effective][:read]
|
|
raise Exception, "You do not have read permissions here."
|
|
end
|
|
unless parent_profile[:permissions][:effective][:write]
|
|
raise Exception, "You do not have write permissions here."
|
|
end
|
|
end
|
|
if ::GxG::Engine::available_file_descriptors() > 0
|
|
result = ::File::open(the_path, flags)
|
|
else
|
|
raise Exception, "Maximum File Descriptor count exceeded. Aborting"
|
|
end
|
|
end
|
|
else
|
|
if parent_profile[:permissions][:effective][:create]
|
|
if ::GxG::Engine::available_file_descriptors() > 0
|
|
::File::new(the_path, "w", perm).close
|
|
result = ::File::open(the_path, flags)
|
|
# Generate VFS-DB permissions, return result
|
|
self.profile(subpath)
|
|
else
|
|
raise Exception, "Maximum File Descriptor count exceeded. Aborting"
|
|
end
|
|
else
|
|
raise Exception, "You do not have create permissions here"
|
|
end
|
|
end
|
|
else
|
|
raise Exception, "Invalid path"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
|
|
#
|
|
end
|
|
else
|
|
# DB open/create :persisted_hash
|
|
begin
|
|
path_array = self.db_path(subpath.to_s)
|
|
if path_array
|
|
profile = self.profile(subpath.to_s)
|
|
if profile[:permissions][:effective][:read]
|
|
result = @database.retrieve_by_uuid(path_array.last[:uuid], @credential)
|
|
else
|
|
# Review : keep? this breaks the 'if you don't have privs you can't see it - no errors, security through obscurity' idea.
|
|
raise Exception, "You do not have read permissions on this object"
|
|
end
|
|
else
|
|
parent_profile = self.profile(::File::dirname(subpath.to_s))
|
|
if parent_profile
|
|
if parent_profile[:permissions][:effective][:create] == true
|
|
if options[:format]
|
|
if GxG::valid_uuid?(options[:format])
|
|
new_object = @database.new_structure_from_format(@credential,{:uuid => options[:format]})
|
|
else
|
|
new_object = @database.new_structure_from_format(@credential,{:ufs => options[:format]})
|
|
end
|
|
else
|
|
if options[:ufs]
|
|
new_object = @database.new_structure_from_format(@credential,{:ufs => options[:ufs]})
|
|
else
|
|
new_object = @database.try_persist({}, @credential)
|
|
end
|
|
end
|
|
# ### allow to create the object with a specific UUID.
|
|
if ::GxG::valid_uuid?(options[:with_uuid])
|
|
address = new_object.db_address()
|
|
new_object.deactivate
|
|
@database.connector()[(address[:table])].filter({:dbid => address[:dbid]}).first.update({:uuid => options[:with_uuid].to_s})
|
|
new_object = @database.retrieve_by_uuid(options[:with_uuid].to_s.to_sym, @credential)
|
|
end
|
|
#
|
|
new_object.wait_for_reservation
|
|
new_object.set_title(::File::basename(subpath.to_s))
|
|
# ### Fashion a link under the parent uuid.
|
|
new_ordinal = @database.connector()[:array_elements].filter({:parent_uuid => parent_profile[:uuid].to_s}).count
|
|
@database.connector()[:array_elements].insert({:parent_uuid => parent_profile[:uuid].to_s, :version => new_object.version(), :ordinal => new_ordinal, :element => "element_hash", :element_hash_uuid => new_object.uuid.to_s})
|
|
# ### Wrap up and return new object
|
|
new_object.release_reservation
|
|
result = new_object
|
|
else
|
|
raise Exception, "You do not have create permissions here"
|
|
end
|
|
#
|
|
else
|
|
raise Exception, "Invalid path"
|
|
end
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => subpath, :options => options}})
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def mkdir(subpath="", permissions=nil)
|
|
result = false
|
|
if @directory
|
|
if subpath.to_s == "" or subpath.to_s == "/"
|
|
the_path = (@directory.path)
|
|
else
|
|
unless subpath.to_s[0] == "/"
|
|
subpath = ("/" << subpath.to_s)
|
|
end
|
|
the_path = (@directory.path + subpath.to_s).gsub("//", "/")
|
|
end
|
|
perm = 16384
|
|
if permissions.is_a?(::Hash)
|
|
# Best if you pass the entire :permissions hash from a profile query
|
|
"040000".each_char_with_index do |digit, indexer|
|
|
case indexer
|
|
when 0
|
|
when 1
|
|
# TODO: process other flags
|
|
when 2
|
|
when 3
|
|
if permissions[:owner].is_a?(::Hash)
|
|
if permissions[:owner][:execute]
|
|
perm += 256
|
|
end
|
|
if permissions[:owner][:write]
|
|
perm += 128
|
|
end
|
|
if permissions[:owner][:read]
|
|
perm += 64
|
|
end
|
|
end
|
|
when 4
|
|
if permissions[:group].is_a?(::Hash)
|
|
if permissions[:group][:execute]
|
|
perm += 32
|
|
end
|
|
if permissions[:group][:write]
|
|
perm += 16
|
|
end
|
|
if permissions[:group][:read]
|
|
perm += 8
|
|
end
|
|
end
|
|
when 5
|
|
if permissions[:other].is_a?(::Hash)
|
|
if permissions[:other][:execute]
|
|
perm += 4
|
|
end
|
|
if permissions[:other][:write]
|
|
perm += 2
|
|
end
|
|
if permissions[:other][:read]
|
|
perm += 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
#
|
|
else
|
|
base_profile = self.profile("/")
|
|
if base_profile.is_a?(::Hash)
|
|
if base_profile[:mode].is_a?(::String)
|
|
perm = base_profile[:mode].to_i(base=8)
|
|
else
|
|
perm = 16877
|
|
end
|
|
else
|
|
perm = 16877
|
|
end
|
|
end
|
|
# FS mkdir
|
|
begin
|
|
parent_profile = self.profile(::File::dirname(subpath.to_s))
|
|
if parent_profile
|
|
if parent_profile[:permissions][:effective][:create]
|
|
if ::File::exist?(the_path)
|
|
result = true
|
|
else
|
|
if ::Dir::mkdir(the_path, perm) == 0
|
|
# Generate VFS-DB permissions, return result
|
|
self.profile(subpath)
|
|
result = true
|
|
end
|
|
end
|
|
else
|
|
raise Exception, "You do not have create permissions here"
|
|
end
|
|
else
|
|
raise Exception, "Invalid path"
|
|
end
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => the_path, :permissions => permissions}})
|
|
#
|
|
end
|
|
else
|
|
# DB dir create
|
|
begin
|
|
parent_profile = self.profile(::File::dirname(subpath.to_s))
|
|
if parent_profile
|
|
if parent_profile[:permissions][:effective][:create] == true
|
|
new_object = @database.try_persist([], @credential)
|
|
new_object.wait_for_reservation
|
|
new_object.set_title(::File::basename(subpath.to_s))
|
|
# ### Fashion a link under the parent uuid.
|
|
new_ordinal = @database.connector()[:array_elements].filter({:parent_uuid => parent_profile[:uuid].to_s}).count
|
|
@database.connector()[:array_elements].insert({:parent_uuid => parent_profile[:uuid].to_s, :version => new_object.version(), :ordinal => new_ordinal, :element => "element_array", :element_array_uuid => new_object.uuid.to_s})
|
|
# ### Wrap up and return new object
|
|
new_object.release_reservation
|
|
result = true
|
|
else
|
|
raise Exception, "You do not have create permissions here"
|
|
end
|
|
else
|
|
raise Exception, "Invalid path"
|
|
end
|
|
#
|
|
rescue Exception => the_error
|
|
log_error({:error => the_error, :parameters => {:path => subpath, :permissions => permissions}})
|
|
#
|
|
end
|
|
#
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def get_permissions(subpath="", the_credential=nil)
|
|
result = []
|
|
if @directory
|
|
if self.exist?(subpath)
|
|
the_profile = self.profile(subpath)
|
|
if the_profile[:file_id]
|
|
result = GxG::DB[:roles][:vfs].vfs_permission_manifest(the_profile[:file_id], the_credential)
|
|
end
|
|
# ????
|
|
# xxx
|
|
# group_roles = GxG::DB[:authority][:system_credentials]
|
|
# the_record = {:credential => group_roles[:administrators], :permissions => the_profile[:permissions][:owner], :details => {}}
|
|
# role = GxG::DB[:authority].role_fetch({:uuid => the_record[:credential].to_s})
|
|
# group = GxG::DB[:authority].group_fetch({:uuid => role[:group_uuid].to_s})
|
|
# the_record[:details][:role_title] = role[:title]
|
|
# the_record[:details][:group] = group[:uuid].to_s.to_sym
|
|
# the_record[:details][:group_title] = group[:title]
|
|
# result << the_record
|
|
# the_record = {:credential => group_roles[:developers], :permissions => the_profile[:permissions][:group], :details => {}}
|
|
# role = GxG::DB[:authority].role_fetch({:uuid => the_record[:credential].to_s})
|
|
# group = GxG::DB[:authority].group_fetch({:uuid => role[:group_uuid].to_s})
|
|
# the_record[:details][:role_title] = role[:title]
|
|
# the_record[:details][:group] = group[:uuid].to_s.to_sym
|
|
# the_record[:details][:group_title] = group[:title]
|
|
# result << the_record
|
|
# the_record = {:credential => group_roles[:designers], :permissions => the_profile[:permissions][:group], :details => {}}
|
|
# role = GxG::DB[:authority].role_fetch({:uuid => the_record[:credential].to_s})
|
|
# group = GxG::DB[:authority].group_fetch({:uuid => role[:group_uuid].to_s})
|
|
# the_record[:details][:role_title] = role[:title]
|
|
# the_record[:details][:group] = group[:uuid].to_s.to_sym
|
|
# the_record[:details][:group_title] = group[:title]
|
|
# result << the_record
|
|
# the_record = {:credential => :"00000000-0000-4000-0000-000000000000", :permissions => the_profile[:permissions][:other], :details => {}}
|
|
# user = GxG::DB[:authority].user_fetch({:uuid => the_record[:credential].to_s})
|
|
# the_record[:details][:user_title] = user[:user_id]
|
|
# result << the_record
|
|
end
|
|
else
|
|
if self.exist?(subpath)
|
|
the_profile = self.profile(subpath)
|
|
result = @database.element_permissions_by_uuid(the_profile[:uuid])
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def revoke_permissions(subpath="", the_credential=nil)
|
|
# revoke_element_permissions(table=:unspecified, dbid=0, credential=nil)
|
|
result = false
|
|
if @directory
|
|
# Review - all I need is the :file_id : make more efficient
|
|
profile = self.profile(subpath)
|
|
if profile[:file_id]
|
|
result = GxG::DB[:roles][:vfs].destroy_vfs_permission(profile[:file_id], the_credential)
|
|
end
|
|
else
|
|
if self.exist?(subpath)
|
|
the_profile = self.profile(subpath)
|
|
result = @database.revoke_permissions_by_uuid(the_profile[:uuid], the_credential)
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def set_permissions(subpath="", the_credential=nil, the_permissions={})
|
|
result = false
|
|
if @directory
|
|
if subpath.to_s == "" or subpath.to_s == "/"
|
|
the_path = (@directory.path)
|
|
else
|
|
unless subpath.to_s[0] == "/"
|
|
subpath = ("/" << subpath.to_s)
|
|
end
|
|
the_path = (@directory.path + subpath.to_s).gsub("//", "/")
|
|
end
|
|
if ::File::exist?(the_path)
|
|
# Review - all I need is the :file_id : make more efficient
|
|
profile = self.profile(subpath, the_credential)
|
|
the_credentials = []
|
|
owner_roles = [(GxG::DB[:authority][:system_credentials][:administrators]), (GxG::DB[:authority][:system_credentials][:developers]), (GxG::DB[:authority][:system_credentials][:designers])].flatten!
|
|
user_roles = GxG::DB[:authority].user_roles(the_credential).collect {|record| record[:credential]}
|
|
the_role = :other
|
|
user_roles.each do |the_role_credential|
|
|
if owner_roles.include?(the_role_credential)
|
|
the_role = :owner
|
|
the_credentials = owner_roles
|
|
break
|
|
end
|
|
end
|
|
unless the_role == :owner
|
|
if the_credential.to_s == "00000000-0000-4000-0000-000000000000"
|
|
the_role = :other
|
|
the_credentials = [ :"00000000-0000-4000-0000-000000000000" ]
|
|
else
|
|
the_role = :group
|
|
the_credentials = user_roles
|
|
end
|
|
end
|
|
# translate to unix permissions
|
|
old_permission = ::File.stat(the_path).mode.to_s(base=2)
|
|
x = nil
|
|
if ::File.directory?(the_path)
|
|
x = 1
|
|
else
|
|
if the_permissions.keys.include?(:execute)
|
|
if the_permissions[:execute] == true
|
|
x = 1
|
|
else
|
|
x = 0
|
|
end
|
|
end
|
|
end
|
|
w = nil
|
|
if the_permissions.keys.include?(:write)
|
|
if the_permissions[:write] == true
|
|
w = 1
|
|
else
|
|
w = 0
|
|
end
|
|
else
|
|
[:rename, :move, :destroy, :create].each do |the_action|
|
|
if the_permissions.keys.include?(the_action)
|
|
if the_permissions[(the_action)] == true
|
|
w = 1
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if the_permissions.keys.include?(:read)
|
|
if the_permissions[:read] == true
|
|
r = 1
|
|
else
|
|
r = 0
|
|
end
|
|
else
|
|
r = nil
|
|
end
|
|
case the_role
|
|
when :owner
|
|
if x
|
|
old_permission[-7] = x.to_s
|
|
end
|
|
if w
|
|
old_permission[-8] = w.to_s
|
|
end
|
|
if r
|
|
old_permission[-9] = r.to_s
|
|
end
|
|
when :group
|
|
if x
|
|
old_permission[-4] = x.to_s
|
|
end
|
|
if w
|
|
old_permission[-5] = w.to_s
|
|
end
|
|
if r
|
|
old_permission[-6] = r.to_s
|
|
end
|
|
when :other
|
|
if x
|
|
old_permission[-1] = x.to_s
|
|
end
|
|
if w
|
|
old_permission[-2] = w.to_s
|
|
end
|
|
if r
|
|
old_permission[-3] = r.to_s
|
|
end
|
|
end
|
|
::File.chmod(old_permission.to_i(2), the_path)
|
|
if profile[:file_id]
|
|
the_credentials.each do |credential_uuid|
|
|
GxG::DB[:roles][:vfs].update_vfs_permission(profile[:file_id], credential_uuid, the_permissions)
|
|
end
|
|
end
|
|
result = true
|
|
end
|
|
else
|
|
# DB object exist?
|
|
path_array = self.db_path(subpath.to_s)
|
|
if path_array
|
|
object_details = {:uuid => path_array.last[:uuid], :table => :unspecified, :dbid => 0}
|
|
header = @database.connector()[:element_hash].filter({:uuid => object_details[:uuid].to_s}).first
|
|
if header
|
|
object_details[:table] = :element_hash
|
|
object_details[:dbid] = header[:dbid]
|
|
else
|
|
header = @database.connector()[:element_array].filter({:uuid => object_details[:uuid].to_s}).first
|
|
if header
|
|
object_details[:table] = :element_array
|
|
object_details[:dbid] = header[:dbid]
|
|
end
|
|
end
|
|
if object_details[:uuid].to_s.size > 0
|
|
if object_details[:table] = :element_array
|
|
manifest = [{:table => object_details[:table], :dbid => object_details[:dbid]}]
|
|
else
|
|
# Note: for an element_hash - ensure all sub-objects are extended this permission.
|
|
manifest = []
|
|
structures = [{:table => object_details[:table], :dbid => object_details[:dbid]}]
|
|
while structures.size > 0 do
|
|
entry = structures[0]
|
|
@database.element_manifest(entry[:table],entry[:dbid]).each do |record|
|
|
if [:element_hash, :element_array].include?(record[:table])
|
|
unless structures.include?(record)
|
|
structures << record
|
|
end
|
|
end
|
|
end
|
|
manifest << structures.shift
|
|
end
|
|
end
|
|
#
|
|
manifest.each do |record|
|
|
@database.assign_element_permission(record[:table], record[:dbid], credential, the_permissions)
|
|
end
|
|
result = true
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
#
|
|
def set_permissions_recursive(subpath="", the_credential=nil, the_permissions={})
|
|
the_profile = self.profile((File.expand_path(subpath)).to_s)
|
|
if the_profile
|
|
if [:virtual_directory, :directory, :persisted_array].include?(the_profile[:type])
|
|
paths = [(File.expand_path(subpath))]
|
|
while paths.size > 0 do
|
|
the_path = paths.shift
|
|
the_path_profile = self.profile(the_path.to_s)
|
|
if [:virtual_directory, :directory, :persisted_array].include?(the_path_profile[:type])
|
|
self.entries(the_path, the_credential).each do |a_profile|
|
|
paths << File.expand_path(the_path + "/" + a_profile[:title].to_s)
|
|
end
|
|
self.set_permissions(the_path, the_credential, the_permissions)
|
|
else
|
|
self.set_permissions(the_path, the_credential, the_permissions)
|
|
end
|
|
end
|
|
else
|
|
self.set_permissions(subpath, the_credential, the_permissions)
|
|
end
|
|
end
|
|
true
|
|
end
|
|
#
|
|
end
|
|
end
|
|
VFS = ::GxG::Storage::FileSpace.new
|
|
#
|
|
end
|