#!/usr/bin/ruby ## $Id: winshell.rb 176 2007-11-21 21:35:51Z Sascha $ require 'singleton' # Windows oriented but platform independent file system abstraction. class ShellInterface def initialize @special_folder_ids = { "ALTSTARTUP" => 0x1d, "APPDATA" => 0x1a, "COMMONALTSTARTUP" => 0x1e, "COMMONAPPDATA" => 0x23, "COMMONDESKTOPDIR" => 0x19, "COMMONFAVORITES" => 0x1f, "COMMONPROGRAMS" => 0x17, "COMMONSTARTMENU" => 0x16, "COMMONSTARTUP" => 0x18, "CONTROLS" => 0x3, "COOKIES" => 0x21, "DESKTOP" => 0x0, "DESKTOPDIRECTORY" => 0x10, "FAVORITES" => 0x6, "FONTS" => 0x14, "HISTORY" => 0x22, "INTERNETCACHE" => 0x20, "LOCALAPPDATA" => 0x1c, "MYPICTURES" => 0x27, "NETHOOD" => 0x13, "PERSONAL" => 0x5, "PRINTHOOD" => 0x1b, "PROFILE" => 0x28, "PROGRAMFILES" => 0x26, "PROGRAMS" => 0x2, "RECENT" => 0x8, "SENDTO" => 0x9, "STARTMENU" => 0xb, "STARTUP" => 0x7, "SYSTEM" => 0x25, "TEMPLATES" => 0x15, "WINDOWS" => 0x24 } init_home end def init_home @home = ENV['HOME'] @home = ENV['HOMEPATH'] unless @home unless @home puts 'ShellInterface could not set @home - using /' @home = '/' end end # try to provide a platform independent default def path_for_special_folder(name) return @home end def tmp_path tmp = ENV['TEMP'] tmp = ENV['TMP'] unless tmp tmp = '/' unless tmp return tmp end # platform dependent path (no better default possible) def os_path(pathString) return win_path(pathString) end # for use in commandline execution def escape(pathString) return pathString end # old name, use os_path instead def win_path(pathString) return pathString end # Get a path string usable by common ruby classes such as Dir and File. # Default: convert '\' to '/' on all systems, even on Unix def ruby_path(pathString) return pathString.gsub(/\\/, separator).gsub(/^~/, @home) end def picture_directory return path_for_special_folder("MYPICTURES") end def desktop_directory return path_for_special_folder("DESKTOPDIRECTORY") end def special_folder_listing @special_folder_ids.keys.sort.each do |aFolder| printf "#{aFolder}: " puts "#{path_for_special_folder aFolder}" end end # Get the path separator used by common ruby classes such as Dir and File. def separator return File::SEPARATOR end def remove_path(filename_with_path) return ruby_path(filename_with_path).split(separator)[-1] end # Is filename a Windows shortcut? - Default: no file is a shortcut def link?(filename) return false end # Has filename the Extension ext (e. g. ".jpg") def has_extension?(filename, ext) raise ArgumentError, "Extension missing" if ext.empty? p_ext = (ext[0] == '.'[0] ? ext : '.' + ext).downcase dc_filename = filename.downcase return File.basename(dc_filename, p_ext) != File.basename(dc_filename) end end # Default access to file system abstraction. # A platform dependent singleton will be instantiated. class WinShell < ShellInterface include Singleton end if RUBY_PLATFORM =~ /win32/ or RUBY_PLATFORM =~ /mingw32/ require 'win32ole' class WinShell < ShellInterface def initialize @shell = WIN32OLE.new('Shell.application') begin require 'win32/dir' @os_paths = { "ALTSTARTUP" => Dir::ALTSTARTUP, "APPDATA" => Dir::APPDATA, "COMMONALTSTARTUP" => Dir::COMMON_ALTSTARTUP, "COMMONAPPDATA" => Dir::COMMON_APPDATA, "COMMONDESKTOPDIR" => Dir::COMMON_DESKTOPDIRECTORY, "COMMONFAVORITES" => Dir::COMMON_FAVORITES, "COMMONPROGRAMS" => Dir::COMMON_PROGRAMS, "COMMONSTARTMENU" => Dir::COMMON_STARTMENU, "COMMONSTARTUP" =>Dir::COMMON_STARTUP, "COOKIES" => Dir::COOKIES, "DESKTOP" => Dir::DESKTOP, "DESKTOPDIRECTORY" => Dir::DESKTOPDIRECTORY, "FAVORITES" => Dir::FAVORITES, "FONTS" => Dir::FONTS, "HISTORY" => Dir::HISTORY, "INTERNETCACHE" => Dir::INTERNET_CACHE, "LOCALAPPDATA" => Dir::LOCAL_APPDATA, "MYPICTURES" => Dir::MYPICTURES, "NETHOOD" => Dir::NETHOOD, "PERSONAL" => Dir::PERSONAL, "PRINTHOOD" => Dir::PRINTHOOD, "PROFILE" => Dir::PROFILE, "PROGRAMFILES" => Dir::PROGRAM_FILES, "PROGRAMS" => Dir::PROGRAMS, "RECENT" => Dir::RECENT, "SENDTO" => Dir::SENDTO, "STARTMENU" => Dir::STARTMENU, "STARTUP" => Dir::STARTUP, "SYSTEM" => Dir::SYSTEM, "TEMPLATES" => Dir::TEMPLATES, "WINDOWS" => Dir::WINDOWS } rescue LoadError => load_error @os_paths = {} end super end def path_for_special_folder(name) win32_utils_path = @os_paths[name] return ruby_path(win32_utils_path ? win32_utils_path : path_from_env(name)) end def path_from_env(name) path_in_env = ENV[name] return path_in_env if(path_in_env) if(name=="MYPICTURES") win7 = "#{@home}\\Pictures" return win7 if(File.exists?(ruby_path(win7))) end return @home end # Include virtual paths on vindows def special_folder_listing @special_folder_ids.keys.sort.each do |aFolder| puts "#{aFolder}:" puts "\tPath: #{path_for_special_folder aFolder}" puts "\tVirtual path: #{virtual_path_for aFolder}" end end def virtual_path_for(name) ole_folder = special_folder(name) return path_for_folder(ole_folder) end # Is filename a Windows shortcut? def link?(filename) return has_extension?(filename, '.lnk') end # Get a path string usable by Windows systems. def win_path(pathString) return pathString.gsub(separator, "\\") end # Get a path string usable by common ruby classes such as Dir and File. def ruby_path(pathString) return pathString.gsub(/\\/, separator) end # for use in commandline execution def escape(pathString) return "\"#{pathString}\"" end # Get the path stored in the Windows shortcut filename. # PRE: link?(filename) def get_link_target(filename, path_string = Dir.getwd) raise(ArgumentError, "No link") unless link?(filename) puts "Compute shortcut: #{filename}" filename_only = remove_path(filename) # avoid errors ignore performance location = win_path(path_string) ole_folder = shell.NameSpace(location) raise(ArgumentError, "#{path_string} is no folder") unless ole_folder ole_item = get_link_item(filename_only, ole_folder) return '' unless ole_item return ruby_path(ole_item.GetLink.Path.to_s) end # private; public for better testing only def get_link_item(filename, ole_folder) begin return ole_folder.ParseName(filename) rescue Error => ex puts ex.message end # Fallback to old solution: # Compare filename with all item names in folder. # This didn't work with Vista's shortcut "Sample Pictures" # (The item name in german Vista is "Beispielbilder" # which is not equal to the filename.) ext = '.' + filename.split('.')[-1] linkdef = File.basename(filename, ext) raise(ArgumentError, "Link name empty") if linkdef.empty? puts "Location: #{path_string}" puts "Shortcut name: #{linkdef}" ole_items = ole_folder.items ole_item_col = (0..ole_items.Count-1).collect { |i| ole_items.item(i) } return ole_item_col.find { |fi| fi.isLink && linkdef == (fi.Name.to_s) } end # My shell is the ole object "Shell" as described in the Windows SDK. def shell return @shell end def special_folder(name) id = @special_folder_ids[name] return shell.NameSpace(id) end def path_for_folder(folder) current = folder.ParentFolder path = folder.Title while current do title = current.Title path = title + separator + path current = current.ParentFolder end return path end # Get Directory entries as ole-objects, not as strings! def get_folder_items(directoryPathString) folder = shell.NameSpace(win_path(directoryPathString)) raise "Folderermittlung fehlgeschlagen" if !folder items = folder.items return (0..items.Count-1).collect { |i| items.item(i) } end end end # Example if $0 == __FILE__ def example puts RUBY_PLATFORM shell = WinShell.instance puts shell.class h = "Example 1: WinShell.new.specialFolderListing" h_line = (1..h.size).collect { |c| '*'} puts "#{h_line}\n#{h}\n#{h_line}" shell.special_folder_listing h = "Example 2. Links on Desktop" puts "#{h_line}\n#{h}\n#{h_line}" desktop_dir = shell.desktop_directory links = Dir[desktop_dir + '/*'].select { |p| shell.link?(File.basename(p)) } links.each do |link_path| link_name = File.basename link_path puts "#{link_name} => \"#{shell.get_link_target link_name, desktop_dir}\"" end h = "Example 3. Environment" puts "#{h_line}\n#{h}\n#{h_line}" ENV.each_pair { |key, val| puts("#{key}: #{val}") } end example end