List Info

Thread: Prototyping the Dir class




Prototyping the Dir class
country flaguser name
United States
2007-10-03 23:11:34
Hi all,

I've been prototyping a Windows-only Dir class for Ruby.
Below is what 
I've got so far. The class methods were easy, except for
Dir.glob. The 
code in dir.c is just nasty, and I can't help but think that
it could be 
heavily refactored. I did come across this link:

http://w
ww.codeproject.com/file/fileglob.asp

But I haven't investigated it yet. Anyone who wants to take
a stab at a 
pure Ruby Dir.glob is welcome to try. I made one attempt to
convert the 
pattern to Regexp objects, but it got pretty hairy,
especially with 
'[a-z]' type ranges, i.e. match only one character instead
of any one 
character. I'd be interested to see what techniques anyone
else comes up 
with.

The other issue is how to design Dir.new. I thought the
smart thing to 
do would be to store the handle returned by FindFirstFile(),
but then I 
realized that the handle returned by FindFirstFile() isn't
compatible 
with the handle used for SetFilePointer(), which would make
seek/tell 
difficult to implement. On the other hand, if we use
CreateFile(), its 
handle is not compatible with FindFirstFile() or
FindNextFile(), making 
read/each difficult to implement.

I make two refactoring decisions for now - Dir.foreach has
been skipped. 
The Dir.entries now takes an optional block. Dir.open has
been skipped. 
The Dir.new method now takes an optional block.

Anyway, suggestions welcome on the code below. BTW, you'll
want to grab 
the latest windows-pr code from CVS for some of this code to
work.

Regards,

Dan

# dir.rb

require 'windows/file'
require 'windows/error'
require 'windows/unicode'
require 'windows/directory'
require 'windows/process'
require 'windows/handle'

# Struct sizes (ANSI/Wide)
#
# WIN32_FIND_DATA: 320/592
#    [0,4]    => dwFileAttributes
#    [4,8]   => ftCreationTime
#    [12,8]  => ftLastAccessTime
#    [20,8]  => ftLastWriteTime
#    [28,4]   => nFileSizeHigh
#    [32,4]   => nFileSizeLow
#    [36,4]   => dwReserved0
#    [40,4]   => dwReserved1
#    [44,260/520] => cFileName[MAX_PATH]
#    [304,14] => cAlternateFileName[14]

class MyDir
    include Windows::Error
    include Windows::File
    include Windows::Unicode
    include Windows:irectory

    include Windows::Process
    include Windows::Handle
    extend Windows::Error
    extend Windows::File
    extend Windows::Unicode
    extend Windows:irectory

    extend Windows::Process
    extend Windows::Handle

    MAX_PATH = 260

    def self.chdir(dir = nil, &block)
       if dir.nil?
          buf = 0.chr * 1024 # 32k is the official limit

          if GetEnvironmentVariable('USERPROFILE', buf,
buf.size) == 0
             if GetEnvironmentVariable('HOME', buf,
buf.size) == 0
                raise ArgumentError, 'USERPROFILE/HOME not
set'
             end
          end

          dir = buf.unpack("Z*")[0]
       end

       if block_given?
          begin
             buf = 0.chr * MAX_PATH

             if GetCurrentDirectory(buf.length, buf) == 0
                raise ArgumentError, get_last_error
             else
                # MSDN says the drive letter could be
dropped,
                # and that GetFullPathName should be called
just in case.
                current = buf.unpack("Z*")[0]
                buf2 = 0.chr * MAX_PATH

                if GetFullPathName(current, buf2.length,
buf2, 0) == 0
                   raise ArgumentError, get_last_error
                end

                current = buf2.unpack("Z*")[0]
             end

             unless SetCurrentDirectory(dir)
                raise ArgumentError, get_last_error
             end

             block.call
          ensure
             SetCurrentDirectory(current)
          end
       else
          unless SetCurrentDirectory(dir)
             raise ArgumentError, get_last_error
          end
       end
    end

    def self.delete(dirname)
       unless RemoveDirectory(dirname)
          raise ArgumentError, get_last_error
       end
    end

    # Blend entries and foreach into one method
    def self.entries(dirname)
       dirname += "\*"
       fdata = 0.chr * 320 # 580 if wide
       array = block_given? ? [] : nil

       hfind = FindFirstFile(dirname, fdata)

       if hfind == INVALID_HANDLE_VALUE
          raise ArgumentError, get_last_error
       end

       file = fdata[44, MAX_PATH].unpack("Z*")[0]

       if block_given?
          yield file
       else
          array << file
       end

       while FindNextFile(hfind, fdata)
          file = fdata[44,
MAX_PATH].unpack("Z*")[0]
          if block_given?
          	yield file
          else
             array << file
          end
       end

       error = GetLastError()

       FindClose(hfind)

       if error != ERROR_NO_MORE_FILES
          raise get_last_error(error)
       end

       array
    end

    def self.getwd
       buf = 0.chr * MAX_PATH

       if GetCurrentDirectory(buf.length, buf) == 0
          raise ArgumentError, get_last_error
       end

       buf.unpack("Z*")[0]
    end

    # The 'permissions' could be a Security::Attributes
object
    # of some sort.
    #
    def self.mkdir(dirname, permissions = nil)
       unless CreateDirectory(dirname, permissions)
          raise ArgumentError, get_last_error
       end
    end

    attr_reader :path

    # Blend new and open into one method
    def initialize(path)
       path = path + "\*"
       path.tr!(File::SEPARATOR, File::ALT_SEPARATOR)

       fdata  = 0.chr * 320 # 580 if wide
       handle = FindFirstFile(path, fdata)

       if handle == INVALID_HANDLE_VALUE
           raise ArgumentError, get_last_error
       end

       if block_given?
          begin
             yield handle
          ensure
             close
          end
       end

       pos = 0

       handle
    end

    def close
       FindClose(handle)
    end

    # Broken because the handle isn't valid
    def pos
       SetFilePointer(handle, 0, 0, FILE_CURRENT)
    end

    def read
       if pos > 0
          fdata = 0.chr * 320
          unless FindNextFile(handle, fdata)
             raise ArgumentError, get_last_error
          end
       end
       pos += 1
       fdata[44, MAX_PATH].unpack("Z*")[0]
    end

    def each
       fdata = 0.chr * 320
       while FindNextFile(handle, fdata)
          yield fdata[44,
MAX_PATH].unpack("Z*")[0]
       end
    end

    def rewind
       unless SetFilePointerEx(handle,0,nil,0)
          raise ArgumentError, get_last_error
       end
    end

    # class level aliases

    class << self
       alias open new
       alias foreach entries
       alias pwd getwd
       alias rmdir delete
       alias unlink delete
    end
end

if $0 == __FILE__
    dir = MyDir.new(Dir.pwd)
    p dir.pos
    dir.read
    p dir.pos
    dir.close
end
_______________________________________________
win32utils-devel mailing list
win32utils-develrubyforge.org
http://rubyforge.org/mailman/listinfo/win32utils-devel


[1]

about | contact  Other archives ( Real Estate discussion Medical topics )