171 lines
4.7 KiB
Ruby
171 lines
4.7 KiB
Ruby
|
=begin
|
||
|
|
||
|
full file_system module
|
||
|
will be available and recommended usage, allowing Ole::Storage, Dir, and Zip::ZipFile to be
|
||
|
used pretty exchangably down the track. should be possible to write a recursive copy using
|
||
|
the plain api, such that you can copy dirs/files agnostically between any of ole docs, dirs,
|
||
|
and zip files.
|
||
|
|
||
|
i think its okay to have an api like this on top, but there are certain things that ole
|
||
|
does that aren't captured.
|
||
|
ole::storage can have multiple files with the same name, for example, or with / in the
|
||
|
name, and other things that are probably invalid anyway.
|
||
|
i think this should remain an addon, built on top of my core api.
|
||
|
but still the ideas can be reflected in the core, ie, changing the read/write semantics.
|
||
|
|
||
|
once the core changes are complete, this will be a pretty straight forward file to complete.
|
||
|
|
||
|
=end
|
||
|
|
||
|
module Ole
|
||
|
class Storage
|
||
|
def file
|
||
|
@file ||= FileParent.new self
|
||
|
end
|
||
|
|
||
|
def dir
|
||
|
@dir ||= DirParent.new self
|
||
|
end
|
||
|
|
||
|
def dirent_from_path path_str
|
||
|
path = path_str.sub(/^\/*/, '').sub(/\/*$/, '')
|
||
|
dirent = @root
|
||
|
return dirent if path.empty?
|
||
|
path = path.split /\/+/
|
||
|
until path.empty?
|
||
|
raise "invalid path #{path_str.inspect}" if dirent.file?
|
||
|
if tmp = dirent[path.shift]
|
||
|
dirent = tmp
|
||
|
else
|
||
|
# allow write etc later.
|
||
|
raise "invalid path #{path_str.inspect}"
|
||
|
end
|
||
|
end
|
||
|
dirent
|
||
|
end
|
||
|
|
||
|
class FileParent
|
||
|
def initialize ole
|
||
|
@ole = ole
|
||
|
end
|
||
|
|
||
|
def open path_str, mode='r'
|
||
|
dirent = @ole.dirent_from_path path_str
|
||
|
# like Errno::EISDIR
|
||
|
raise "#{path_str.inspect} is a directory" unless dirent.file?
|
||
|
io = dirent.io
|
||
|
if block_given?
|
||
|
yield io
|
||
|
else
|
||
|
io
|
||
|
end
|
||
|
end
|
||
|
|
||
|
alias new :open
|
||
|
|
||
|
def read path
|
||
|
open(path) { |f| f.read }
|
||
|
end
|
||
|
|
||
|
# crappy copy from Dir.
|
||
|
def unlink path
|
||
|
dirent = @ole.dirent_from_path path
|
||
|
# EPERM
|
||
|
raise "operation not permitted #{path.inspect}" unless dirent.file?
|
||
|
# i think we should free all of our blocks. i think the best way to do that would be
|
||
|
# like:
|
||
|
# open(path) { |f| f.truncate 0 }. which should free all our blocks from the
|
||
|
# allocation table. then if we remove ourself from our parent, we won't be part of
|
||
|
# the bat at save time.
|
||
|
# i think if you run repack, all free blocks should get zeroed.
|
||
|
parent = @ole.dirent_from_path(('/' + path).sub(/\/[^\/]+$/, ''))
|
||
|
parent.children.delete dirent
|
||
|
1 # hmmm. as per ::File ?
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class DirParent
|
||
|
def initialize ole
|
||
|
@ole = ole
|
||
|
end
|
||
|
|
||
|
def open path_str
|
||
|
dirent = @ole.dirent_from_path path_str
|
||
|
# like Errno::ENOTDIR
|
||
|
raise "#{path_str.inspect} is not a directory" unless dirent.dir?
|
||
|
dir = Dir.new dirent, path_str
|
||
|
if block_given?
|
||
|
yield dir
|
||
|
else
|
||
|
dir
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# certain Dir class methods proxy in this fashion:
|
||
|
def entries path
|
||
|
open(path) { |dir| dir.entries }
|
||
|
end
|
||
|
|
||
|
# there are some other important ones, like:
|
||
|
# chroot (!), mkdir, chdir, rmdir, glob etc etc. for now, i think
|
||
|
# mkdir, and rmdir are the main ones we'd need to support
|
||
|
def rmdir path
|
||
|
dirent = @ole.dirent_from_path path
|
||
|
p dirent
|
||
|
# repeating myself
|
||
|
raise "#{path.inspect} is not a directory" unless dirent.dir?
|
||
|
# ENOTEMPTY:
|
||
|
raise "directory not empty #{path.inspect}" unless dirent.children.empty?
|
||
|
# now delete it, how to do that? the canonical representation that is
|
||
|
# maintained is the root tree, and the children array. we must remove it
|
||
|
# from the children array.
|
||
|
# we need the parent then. this sucks but anyway:
|
||
|
parent = @ole.dirent_from_path path.sub(/\/[^\/]+$/, '') || '/'
|
||
|
# note that the way this currently works, on save and repack time this will get
|
||
|
# reflected. to work properly, ie to make a difference now it would have to re-write
|
||
|
# the dirent. i think that Ole::Storage#close will handle that. and maybe include a
|
||
|
# #repack.
|
||
|
parent.children.delete dirent
|
||
|
0 # hmmm. as per ::Dir ?
|
||
|
end
|
||
|
|
||
|
class Dir
|
||
|
include Enumerable
|
||
|
attr_reader :dirent, :path, :entries, :pos
|
||
|
|
||
|
def initialize dirent, path
|
||
|
@dirent, @path = dirent, path
|
||
|
@pos = 0
|
||
|
# FIXME: hack, and probably not really desired
|
||
|
@entries = %w[. ..] + @dirent.children.map(&:name)
|
||
|
end
|
||
|
|
||
|
def each(&block)
|
||
|
@entries.each(&block)
|
||
|
end
|
||
|
|
||
|
def close
|
||
|
end
|
||
|
|
||
|
def read
|
||
|
@entries[@pos]
|
||
|
ensure
|
||
|
@pos += 1 if @pos < @entries.length
|
||
|
end
|
||
|
|
||
|
def pos= pos
|
||
|
@pos = [[0, pos].max, @entries.length].min
|
||
|
end
|
||
|
|
||
|
def rewind
|
||
|
@pos = 0
|
||
|
end
|
||
|
|
||
|
alias tell :pos
|
||
|
alias seek :pos=
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|