win32-dir 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -1,38 +1,34 @@
1
- require 'rake'
2
- require 'rake/clean'
3
- require 'rake/testtask'
4
-
5
- CLEAN.include('**/*.gem', '**/*.log')
6
-
7
- namespace 'gem' do
8
- desc "Create the win32-dir gem"
9
- task :create => [:clean] do
10
- Dir["*.gem"].each{ |f| File.delete(f) }
11
- spec = eval(IO.read('win32-dir.gemspec'))
12
-
13
- if Gem::VERSION.to_f < 2.0
14
- Gem::Builder.new(spec).build
15
- else
16
- require 'rubygems/package'
17
- Gem::Package.build(spec)
18
- end
19
- end
20
-
21
- desc "Install the win32-dir gem"
22
- task :install => [:create] do
23
- file = Dir["*.gem"].first
24
- sh "gem install -l #{file}"
25
- end
26
- end
27
-
28
- desc "Run the example program"
29
- task :example do
30
- sh "ruby -Ilib examples/dir_example.rb"
31
- end
32
-
33
- Rake::TestTask.new do |t|
34
- t.warning = true
35
- t.verbose = true
36
- end
37
-
38
- task :default => :test
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/testtask'
4
+
5
+ CLEAN.include('**/*.gem', '**/*.log')
6
+
7
+ namespace 'gem' do
8
+ desc "Create the win32-dir gem"
9
+ task :create => [:clean] do
10
+ require 'rubygems/package'
11
+ Dir["*.gem"].each{ |f| File.delete(f) }
12
+ spec = eval(IO.read('win32-dir.gemspec'))
13
+ spec.signing_key = File.join(Dir.home, '.ssh', 'gem-private_key.pem')
14
+ Gem::Package.build(spec)
15
+ end
16
+
17
+ desc "Install the win32-dir gem"
18
+ task :install => [:create] do
19
+ file = Dir["*.gem"].first
20
+ sh "gem install -l #{file}"
21
+ end
22
+ end
23
+
24
+ desc "Run the example program"
25
+ task :example do
26
+ sh "ruby -Ilib examples/dir_example.rb"
27
+ end
28
+
29
+ Rake::TestTask.new do |t|
30
+ t.warning = true
31
+ t.verbose = true
32
+ end
33
+
34
+ task :default => :test
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDcDCCAligAwIBAgIBATANBgkqhkiG9w0BAQUFADA/MREwDwYDVQQDDAhkamJl
3
+ cmc5NjEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYDY29t
4
+ MB4XDTE1MDkwMjIwNDkxOFoXDTE2MDkwMTIwNDkxOFowPzERMA8GA1UEAwwIZGpi
5
+ ZXJnOTYxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
6
+ bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMyTkvXqRp6hLs9eoJOS
7
+ Hmi8kRYbq9Vkf15/hMxJpotYMgJVHHWrmDcC5Dye2PbnXjTkKf266Zw0PtT9h+lI
8
+ S3ts9HO+vaCFSMwFFZmnWJSpQ3CNw2RcHxjWkk9yF7imEM8Kz9ojhiDXzBetdV6M
9
+ gr0lV/alUr7TNVBDngbXEfTWscyXh1qd7xZ4EcOdsDktCe5G45N/o3662tPQvJsi
10
+ FOF0CM/KuBsa/HL1/eoEmF4B3EKIRfTHrQ3hu20Kv3RJ88QM4ec2+0dd97uX693O
11
+ zv6981fyEg+aXLkxrkViM/tz2qR2ZE0jPhHTREPYeMEgptRkTmWSKAuLVWrJEfgl
12
+ DtkCAwEAAaN3MHUwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFEwe
13
+ nn6bfJADmuIDiMSOzedOrL+xMB0GA1UdEQQWMBSBEmRqYmVyZzk2QGdtYWlsLmNv
14
+ bTAdBgNVHRIEFjAUgRJkamJlcmc5NkBnbWFpbC5jb20wDQYJKoZIhvcNAQEFBQAD
15
+ ggEBAHmNOCWoDVD75zHFueY0viwGDVP1BNGFC+yXcb7u2GlK+nEMCORqzURbYPf7
16
+ tL+/hzmePIRz7i30UM//64GI1NLv9jl7nIwjhPpXpf7/lu2I9hOTsvwSumb5UiKC
17
+ /sqBxI3sfj9pr79Wpv4MuikX1XPik7Ncb7NPsJPw06Lvyc3Hkg5X2XpPtLtS+Gr2
18
+ wKJnmzb5rIPS1cmsqv0M9LPWflzfwoZ/SpnmhagP+g05p8bRNKjZSA2iImM/GyYZ
19
+ EJYzxdPOrx2n6NYR3Hk+vHP0U7UBSveI6+qx+ndQYaeyCn+GRX2PKS9h66YF/Q1V
20
+ tGSHgAmcLlkdGgan182qsE/4kKM=
21
+ -----END CERTIFICATE-----
@@ -1,23 +1,23 @@
1
- ####################################################################
2
- # dir_example.rb
3
- #
4
- # Generic test script for general futzing. Modify as you see fit.
5
- # You can run this via the 'rake example' task.
6
- ####################################################################
7
- require 'win32/dir'
8
-
9
- puts "Admin Tools:\t\t" + Dir::ADMINTOOLS
10
- puts "Common Admin Tools:\t" + Dir::COMMON_ADMINTOOLS
11
- puts "App Data:\t\t" + Dir::APPDATA
12
- puts "Common App Data:\t" + Dir::COMMON_APPDATA
13
- puts "Common Documents:\t" + Dir::COMMON_DOCUMENTS
14
- puts "Cookies:\t\t" + Dir::COOKIES
15
- puts "History:\t\t" + Dir::HISTORY
16
- puts "Internet Cache:\t\t" + Dir::INTERNET_CACHE
17
- puts "Local App Data:\t\t" + Dir::LOCAL_APPDATA
18
- puts "My Pictures:\t\t" + Dir::MYPICTURES
19
- puts "Personal:\t\t" + Dir::PERSONAL
20
- puts "Program Files:\t\t" + Dir::PROGRAM_FILES
21
- puts "Program Files Common:\t" + Dir::PROGRAM_FILES_COMMON
22
- puts "System:\t\t\t" + Dir::SYSTEM
1
+ ####################################################################
2
+ # dir_example.rb
3
+ #
4
+ # Generic test script for general futzing. Modify as you see fit.
5
+ # You can run this via the 'rake example' task.
6
+ ####################################################################
7
+ require 'win32/dir'
8
+
9
+ puts "Admin Tools:\t\t" + Dir::ADMINTOOLS
10
+ puts "Common Admin Tools:\t" + Dir::COMMON_ADMINTOOLS
11
+ puts "App Data:\t\t" + Dir::APPDATA
12
+ puts "Common App Data:\t" + Dir::COMMON_APPDATA
13
+ puts "Common Documents:\t" + Dir::COMMON_DOCUMENTS
14
+ puts "Cookies:\t\t" + Dir::COOKIES
15
+ puts "History:\t\t" + Dir::HISTORY
16
+ puts "Internet Cache:\t\t" + Dir::INTERNET_CACHE
17
+ puts "Local App Data:\t\t" + Dir::LOCAL_APPDATA
18
+ puts "My Pictures:\t\t" + Dir::MYPICTURES
19
+ puts "Personal:\t\t" + Dir::PERSONAL
20
+ puts "Program Files:\t\t" + Dir::PROGRAM_FILES
21
+ puts "Program Files Common:\t" + Dir::PROGRAM_FILES_COMMON
22
+ puts "System:\t\t\t" + Dir::SYSTEM
23
23
  puts "Windows:\t\t" + Dir::WINDOWS
@@ -0,0 +1 @@
1
+ require_relative 'win32/dir'
@@ -1,417 +1,417 @@
1
- require_relative 'dir/constants'
2
- require_relative 'dir/functions'
3
- require_relative 'dir/structs'
4
-
5
- class Dir
6
- include Dir::Structs
7
- include Dir::Constants
8
- extend Dir::Functions
9
-
10
- # The version of the win32-dir library.
11
- VERSION = '0.5.0'
12
-
13
- # CSIDL constants
14
- csidl = Hash[
15
- 'DESKTOP', 0x0000,
16
- 'INTERNET', 0x0001,
17
- 'PROGRAMS', 0x0002,
18
- 'CONTROLS', 0x0003,
19
- 'PRINTERS', 0x0004,
20
- 'PERSONAL', 0x0005,
21
- 'FAVORITES', 0x0006,
22
- 'STARTUP', 0x0007,
23
- 'RECENT', 0x0008,
24
- 'SENDTO', 0x0009,
25
- 'BITBUCKET', 0x000a,
26
- 'STARTMENU', 0x000b,
27
- 'MYDOCUMENTS', 0x000c,
28
- 'MYMUSIC', 0x000d,
29
- 'MYVIDEO', 0x000e,
30
- 'DESKTOPDIRECTORY', 0x0010,
31
- 'DRIVES', 0x0011,
32
- 'NETWORK', 0x0012,
33
- 'NETHOOD', 0x0013,
34
- 'FONTS', 0x0014,
35
- 'TEMPLATES', 0x0015,
36
- 'COMMON_STARTMENU', 0x0016,
37
- 'COMMON_PROGRAMS', 0X0017,
38
- 'COMMON_STARTUP', 0x0018,
39
- 'COMMON_FAVORITES', 0x001f,
40
- 'COMMON_DESKTOPDIRECTORY', 0x0019,
41
- 'APPDATA', 0x001a,
42
- 'PRINTHOOD', 0x001b,
43
- 'LOCAL_APPDATA', 0x001c,
44
- 'ALTSTARTUP', 0x001d,
45
- 'COMMON_ALTSTARTUP', 0x001e,
46
- 'INTERNET_CACHE', 0x0020,
47
- 'COOKIES', 0x0021,
48
- 'HISTORY', 0x0022,
49
- 'COMMON_APPDATA', 0x0023,
50
- 'WINDOWS', 0x0024,
51
- 'SYSTEM', 0x0025,
52
- 'PROGRAM_FILES', 0x0026,
53
- 'MYPICTURES', 0x0027,
54
- 'PROFILE', 0x0028,
55
- 'SYSTEMX86', 0x0029,
56
- 'PROGRAM_FILESX86', 0x002a,
57
- 'PROGRAM_FILES_COMMON', 0x002b,
58
- 'PROGRAM_FILES_COMMONX86', 0x002c,
59
- 'COMMON_TEMPLATES', 0x002d,
60
- 'COMMON_DOCUMENTS', 0x002e,
61
- 'CONNECTIONS', 0x0031,
62
- 'COMMON_MUSIC', 0x0035,
63
- 'COMMON_PICTURES', 0x0036,
64
- 'COMMON_VIDEO', 0x0037,
65
- 'RESOURCES', 0x0038,
66
- 'RESOURCES_LOCALIZED', 0x0039,
67
- 'COMMON_OEM_LINKS', 0x003a,
68
- 'CDBURN_AREA', 0x003b,
69
- 'COMMON_ADMINTOOLS', 0x002f,
70
- 'ADMINTOOLS', 0x0030
71
- ]
72
-
73
- # Dynamically set each of the CSIDL constants
74
- csidl.each{ |key, value|
75
- buf = 0.chr * 1024
76
- path = nil
77
- buf.encode!('UTF-16LE')
78
-
79
- if SHGetFolderPathW(0, value, 0, 0, buf) == 0 # Current path
80
- path = buf.strip
81
- elsif SHGetFolderPathW(0, value, 0, 1, buf) == 0 # Default path
82
- path = buf.strip
83
- else
84
- ptr = FFI::MemoryPointer.new(:long)
85
- info = SHFILEINFO.new
86
- flags = SHGFI_DISPLAYNAME | SHGFI_PIDL
87
-
88
- if SHGetFolderLocation(0, value, 0, 0, ptr) == 0
89
- if SHGetFileInfo(ptr.read_long, 0, info, info.size, flags) != 0
90
- path = info[:szDisplayName].to_s
91
- path.force_encoding(Encoding.default_external)
92
- end
93
- end
94
- end
95
-
96
- begin
97
- Dir.const_set(key, path.encode(Encoding.default_external)) if path
98
- rescue Encoding::UndefinedConversionError
99
- Dir.const_set(key, path.encode('UTF-8')) if path
100
- end
101
- }
102
-
103
- # Set Dir::MYDOCUMENTS to the same as Dir::PERSONAL if undefined
104
- unless defined? MYDOCUMENTS
105
- MYDOCUMENTS = PERSONAL
106
- end
107
-
108
- class << self
109
- alias old_glob glob
110
-
111
- # Same as the standard MRI Dir.glob method except that it handles
112
- # backslashes in path names.
113
- #
114
- def glob(glob_pattern, flags = 0, &block)
115
- if glob_pattern.is_a?(Array)
116
- temp = glob_pattern.map!{ |pattern| string_check(pattern).tr("\\", "/") }
117
- else
118
- temp = string_check(glob_pattern).tr("\\", "/")
119
- end
120
-
121
- old_glob(temp, flags, &block)
122
- end
123
-
124
- alias old_ref []
125
-
126
- # Same as the standard MRI Dir[] method except that it handles
127
- # backslashes in path names.
128
- #
129
- def [](*glob_patterns)
130
- temp = glob_patterns.map!{ |pattern| "#{pattern}".tr("\\", "/") }
131
- old_ref(*temp)
132
- end
133
-
134
- # JRuby normalizes the path by default.
135
- unless RUBY_PLATFORM == 'java'
136
- alias oldgetwd getwd
137
- alias oldpwd pwd
138
-
139
- # Returns the present working directory. Unlike MRI, this method always
140
- # normalizes the path.
141
- #
142
- # Examples:
143
- #
144
- # Dir.chdir("C:/Progra~1")
145
- # Dir.getwd # => C:\Program Files
146
- #
147
- # Dir.chdir("C:/PROGRAM FILES")
148
- # Dir.getwd # => C:\Program Files
149
- #
150
- def getwd
151
- path1 = FFI::Buffer.new(:wint_t, 1024, true)
152
- path2 = FFI::Buffer.new(:wint_t, 1024, true)
153
- path3 = FFI::Buffer.new(:wint_t, 1024, true)
154
-
155
- length = GetCurrentDirectoryW(path1.size, path1)
156
-
157
- if length == 0 || length > path1.size
158
- raise SystemCallError.new("GetCurrentDirectoryW", FFI.errno)
159
- end
160
-
161
- length = GetShortPathNameW(path1, path2, path2.size)
162
-
163
- if length == 0 || length > path2.size
164
- raise SystemCallError.new("GetShortPathNameW", FFI.errno)
165
- end
166
-
167
- length = GetLongPathNameW(path2, path3, path3.size)
168
-
169
- if length == 0 || length > path3.size
170
- raise SystemCallError.new("GetLongPathNameW", FFI.errno)
171
- end
172
-
173
- path = path3.read_bytes(length * 2).wstrip
174
-
175
- begin
176
- path.encode(Encoding.default_external)
177
- rescue Encoding::UndefinedConversionError
178
- path.encode('UTF-8')
179
- end
180
- end
181
-
182
- alias :pwd :getwd
183
- end
184
- end
185
-
186
- # Creates the symlink +to+, linked to the existing directory +from+. If the
187
- # +to+ directory already exists, it must be empty or an error is raised.
188
- #
189
- # Example:
190
- #
191
- # Dir.mkdir('C:/from')
192
- # Dir.create_junction('C:/to', 'C:/from')
193
- #
194
- def self.create_junction(to, from)
195
- to = string_check(to).wincode
196
- from = string_check(from).wincode
197
-
198
- from_path = (0.chr * 1024).encode('UTF-16LE')
199
-
200
- length = GetFullPathNameW(from, from_path.size, from_path, nil)
201
-
202
- if length == 0 || length > from_path.size
203
- raise SystemCallError.new("GetFullPathNameW", FFI.errno)
204
- else
205
- from_path.strip!
206
- end
207
-
208
- to_path = (0.chr * 1024).encode('UTF-16LE')
209
-
210
- length = GetFullPathNameW(to, to_path.size, to_path, nil)
211
-
212
- if length == 0 || length > to_path.size
213
- raise SystemCallError.new("GetFullPathNameW", FFI.errno)
214
- else
215
- to_path.strip!
216
- end
217
-
218
- # You can create a junction to a directory that already exists, so
219
- # long as it's empty.
220
- unless CreateDirectoryW(to_path, nil)
221
- if FFI.errno != ERROR_ALREADY_EXISTS
222
- raise SystemCallError.new("CreateDirectoryW", FFI.errno)
223
- end
224
- end
225
-
226
- begin
227
- # Generic read & write + open existing + reparse point & backup semantics
228
- handle = CreateFileW(
229
- to_path,
230
- GENERIC_READ | GENERIC_WRITE,
231
- 0,
232
- nil,
233
- OPEN_EXISTING,
234
- FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
235
- 0
236
- )
237
-
238
- if handle == INVALID_HANDLE_VALUE
239
- raise SystemCallError.new("CreateFileW", FFI.errno)
240
- end
241
-
242
- target = "\\??\\".encode('UTF-16LE') + from_path
243
-
244
- rdb = REPARSE_JDATA_BUFFER.new
245
- rdb[:ReparseTag] = 2684354563 # IO_REPARSE_TAG_MOUNT_POINT
246
- rdb[:ReparseDataLength] = target.bytesize + 12
247
- rdb[:Reserved] = 0
248
- rdb[:SubstituteNameOffset] = 0
249
- rdb[:SubstituteNameLength] = target.bytesize
250
- rdb[:PrintNameOffset] = target.bytesize + 2
251
- rdb[:PrintNameLength] = 0
252
- rdb[:PathBuffer] = target
253
-
254
- bytes = FFI::MemoryPointer.new(:ulong)
255
-
256
- begin
257
- bool = DeviceIoControl(
258
- handle,
259
- CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, 0),
260
- rdb,
261
- rdb[:ReparseDataLength] + rdb.header_size,
262
- nil,
263
- 0,
264
- bytes,
265
- nil
266
- )
267
-
268
- error = FFI.errno
269
-
270
- unless bool
271
- RemoveDirectoryW(to_path)
272
- raise SystemCallError.new("DeviceIoControl", error)
273
- end
274
- ensure
275
- CloseHandle(handle)
276
- end
277
- end
278
-
279
- self
280
- end
281
-
282
- # Returns the path that a given junction points to. Raises an
283
- # Errno::ENOENT error if the given path does not exist. Returns false
284
- # if it is not a junction.
285
- #
286
- # Example:
287
- #
288
- # Dir.mkdir('C:/from')
289
- # Dir.create_junction('C:/to', 'C:/from')
290
- # Dir.read_junction("c:/to") # => "c:/from"
291
- #
292
- def self.read_junction(junction)
293
- return false unless Dir.junction?(junction)
294
-
295
- junction = string_check(junction).wincode
296
-
297
- junction_path = (0.chr * 1024).encode('UTF-16LE')
298
-
299
- length = GetFullPathNameW(junction, junction_path.size, junction_path, nil)
300
-
301
- if length == 0 || length > junction_path.size
302
- raise SystemCallError.new("GetFullPathNameW", FFI.errno)
303
- else
304
- junction_path.strip!
305
- end
306
-
307
- begin
308
- # Generic read & write + open existing + reparse point & backup semantics
309
- handle = CreateFileW(
310
- junction_path,
311
- GENERIC_READ | GENERIC_WRITE,
312
- 0,
313
- nil,
314
- OPEN_EXISTING,
315
- FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
316
- 0
317
- )
318
-
319
- if handle == INVALID_HANDLE_VALUE
320
- raise SystemCallError.new("CreateFileW", FFI.errno)
321
- end
322
-
323
- rdb = REPARSE_JDATA_BUFFER.new
324
- rdb[:ReparseTag] = 0
325
- rdb[:ReparseDataLength] = 0
326
- rdb[:Reserved] = 0
327
- rdb[:SubstituteNameOffset] = 0
328
- rdb[:SubstituteNameLength] = 0
329
- rdb[:PrintNameOffset] = 0
330
- rdb[:PrintNameLength] = 0
331
- rdb[:PathBuffer] = ''
332
-
333
- bytes = FFI::MemoryPointer.new(:ulong)
334
-
335
- begin
336
- bool = DeviceIoControl(
337
- handle,
338
- CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, 0),
339
- nil,
340
- 0,
341
- rdb,
342
- 1024,
343
- bytes,
344
- nil
345
- )
346
-
347
- error = FFI.errno
348
-
349
- unless bool
350
- raise SystemCallError.new("DeviceIoControl", error)
351
- end
352
- ensure
353
- CloseHandle(handle)
354
- end
355
- end
356
-
357
- # MSDN says print and substitute names can be in any order
358
- jname = (rdb[:PathBuffer].to_ptr + rdb[:SubstituteNameOffset]).read_string(rdb[:SubstituteNameLength])
359
- jname = jname.bytes.to_a.pack('C*')
360
- jname = jname.force_encoding("UTF-16LE")
361
- raise "Invalid junction name: #{jname.encode('UTF-8')}" unless jname[0..3] == "\\??\\".encode("UTF-16LE")
362
- begin
363
- File.expand_path(jname[4..-1].encode(Encoding.default_external))
364
- rescue Encoding::UndefinedConversionError
365
- File.expand_path(jname[4..-1].encode('UTF-8'))
366
- end
367
- end
368
-
369
- # Returns whether or not +path+ is empty. Returns false if +path+ is not
370
- # a directory, or contains any files other than '.' or '..'.
371
- #
372
- def self.empty?(path)
373
- PathIsDirectoryEmptyW("#{path}".wincode)
374
- end
375
-
376
- # Returns whether or not +path+ is a junction.
377
- #
378
- def self.junction?(path)
379
- string_check(path)
380
- bool = true
381
-
382
- attrib = GetFileAttributesW("#{path}".wincode)
383
-
384
- # Only directories with a reparse point attribute can be junctions
385
- if attrib == INVALID_FILE_ATTRIBUTES ||
386
- attrib & FILE_ATTRIBUTE_DIRECTORY == 0 ||
387
- attrib & FILE_ATTRIBUTE_REPARSE_POINT == 0
388
- then
389
- bool = false
390
- end
391
-
392
- bool
393
- end
394
-
395
- # Class level aliases
396
- #
397
- class << self
398
- alias reparse_dir? junction?
399
- end
400
-
401
- private
402
-
403
- class << self
404
- # Simulate MRI's contortions for a stringiness check.
405
- def string_check(arg)
406
- return arg if arg.is_a?(String)
407
- return arg.send(:to_str) if arg.respond_to?(:to_str, true) # MRI honors it, even if private
408
- return arg.to_path if arg.respond_to?(:to_path)
409
- raise TypeError
410
- end
411
- end
412
-
413
- # Macro from Windows header file, used by the create_junction method.
414
- def self.CTL_CODE(device, function, method, access)
415
- ((device) << 16) | ((access) << 14) | ((function) << 2) | (method)
416
- end
417
- end
1
+ require_relative 'dir/constants'
2
+ require_relative 'dir/functions'
3
+ require_relative 'dir/structs'
4
+
5
+ class Dir
6
+ include Dir::Structs
7
+ include Dir::Constants
8
+ extend Dir::Functions
9
+
10
+ # The version of the win32-dir library.
11
+ VERSION = '0.5.1'
12
+
13
+ # CSIDL constants
14
+ csidl = Hash[
15
+ 'DESKTOP', 0x0000,
16
+ 'INTERNET', 0x0001,
17
+ 'PROGRAMS', 0x0002,
18
+ 'CONTROLS', 0x0003,
19
+ 'PRINTERS', 0x0004,
20
+ 'PERSONAL', 0x0005,
21
+ 'FAVORITES', 0x0006,
22
+ 'STARTUP', 0x0007,
23
+ 'RECENT', 0x0008,
24
+ 'SENDTO', 0x0009,
25
+ 'BITBUCKET', 0x000a,
26
+ 'STARTMENU', 0x000b,
27
+ 'MYDOCUMENTS', 0x000c,
28
+ 'MYMUSIC', 0x000d,
29
+ 'MYVIDEO', 0x000e,
30
+ 'DESKTOPDIRECTORY', 0x0010,
31
+ 'DRIVES', 0x0011,
32
+ 'NETWORK', 0x0012,
33
+ 'NETHOOD', 0x0013,
34
+ 'FONTS', 0x0014,
35
+ 'TEMPLATES', 0x0015,
36
+ 'COMMON_STARTMENU', 0x0016,
37
+ 'COMMON_PROGRAMS', 0X0017,
38
+ 'COMMON_STARTUP', 0x0018,
39
+ 'COMMON_FAVORITES', 0x001f,
40
+ 'COMMON_DESKTOPDIRECTORY', 0x0019,
41
+ 'APPDATA', 0x001a,
42
+ 'PRINTHOOD', 0x001b,
43
+ 'LOCAL_APPDATA', 0x001c,
44
+ 'ALTSTARTUP', 0x001d,
45
+ 'COMMON_ALTSTARTUP', 0x001e,
46
+ 'INTERNET_CACHE', 0x0020,
47
+ 'COOKIES', 0x0021,
48
+ 'HISTORY', 0x0022,
49
+ 'COMMON_APPDATA', 0x0023,
50
+ 'WINDOWS', 0x0024,
51
+ 'SYSTEM', 0x0025,
52
+ 'PROGRAM_FILES', 0x0026,
53
+ 'MYPICTURES', 0x0027,
54
+ 'PROFILE', 0x0028,
55
+ 'SYSTEMX86', 0x0029,
56
+ 'PROGRAM_FILESX86', 0x002a,
57
+ 'PROGRAM_FILES_COMMON', 0x002b,
58
+ 'PROGRAM_FILES_COMMONX86', 0x002c,
59
+ 'COMMON_TEMPLATES', 0x002d,
60
+ 'COMMON_DOCUMENTS', 0x002e,
61
+ 'CONNECTIONS', 0x0031,
62
+ 'COMMON_MUSIC', 0x0035,
63
+ 'COMMON_PICTURES', 0x0036,
64
+ 'COMMON_VIDEO', 0x0037,
65
+ 'RESOURCES', 0x0038,
66
+ 'RESOURCES_LOCALIZED', 0x0039,
67
+ 'COMMON_OEM_LINKS', 0x003a,
68
+ 'CDBURN_AREA', 0x003b,
69
+ 'COMMON_ADMINTOOLS', 0x002f,
70
+ 'ADMINTOOLS', 0x0030
71
+ ]
72
+
73
+ # Dynamically set each of the CSIDL constants
74
+ csidl.each{ |key, value|
75
+ buf = 0.chr * 1024
76
+ path = nil
77
+ buf.encode!('UTF-16LE')
78
+
79
+ if SHGetFolderPathW(0, value, 0, 0, buf) == 0 # Current path
80
+ path = buf.strip
81
+ elsif SHGetFolderPathW(0, value, 0, 1, buf) == 0 # Default path
82
+ path = buf.strip
83
+ else
84
+ ptr = FFI::MemoryPointer.new(:long)
85
+ info = SHFILEINFO.new
86
+ flags = SHGFI_DISPLAYNAME | SHGFI_PIDL
87
+
88
+ if SHGetFolderLocation(0, value, 0, 0, ptr) == 0
89
+ if SHGetFileInfo(ptr.read_long, 0, info, info.size, flags) != 0
90
+ path = info[:szDisplayName].to_s
91
+ path.force_encoding(Encoding.default_external)
92
+ end
93
+ end
94
+ end
95
+
96
+ begin
97
+ Dir.const_set(key, path.encode(Encoding.default_external)) if path
98
+ rescue Encoding::UndefinedConversionError
99
+ Dir.const_set(key, path.encode('UTF-8')) if path
100
+ end
101
+ }
102
+
103
+ # Set Dir::MYDOCUMENTS to the same as Dir::PERSONAL if undefined
104
+ unless defined? MYDOCUMENTS
105
+ MYDOCUMENTS = PERSONAL
106
+ end
107
+
108
+ class << self
109
+ alias old_glob glob
110
+
111
+ # Same as the standard MRI Dir.glob method except that it handles
112
+ # backslashes in path names.
113
+ #
114
+ def glob(glob_pattern, flags = 0, &block)
115
+ if glob_pattern.is_a?(Array)
116
+ temp = glob_pattern.map!{ |pattern| string_check(pattern).tr("\\", "/") }
117
+ else
118
+ temp = string_check(glob_pattern).tr("\\", "/")
119
+ end
120
+
121
+ old_glob(temp, flags, &block)
122
+ end
123
+
124
+ alias old_ref []
125
+
126
+ # Same as the standard MRI Dir[] method except that it handles
127
+ # backslashes in path names.
128
+ #
129
+ def [](*glob_patterns)
130
+ temp = glob_patterns.map!{ |pattern| "#{pattern}".tr("\\", "/") }
131
+ old_ref(*temp)
132
+ end
133
+
134
+ # JRuby normalizes the path by default.
135
+ unless RUBY_PLATFORM == 'java'
136
+ alias oldgetwd getwd
137
+ alias oldpwd pwd
138
+
139
+ # Returns the present working directory. Unlike MRI, this method always
140
+ # normalizes the path.
141
+ #
142
+ # Examples:
143
+ #
144
+ # Dir.chdir("C:/Progra~1")
145
+ # Dir.getwd # => C:\Program Files
146
+ #
147
+ # Dir.chdir("C:/PROGRAM FILES")
148
+ # Dir.getwd # => C:\Program Files
149
+ #
150
+ def getwd
151
+ path1 = FFI::Buffer.new(:wint_t, 1024, true)
152
+ path2 = FFI::Buffer.new(:wint_t, 1024, true)
153
+ path3 = FFI::Buffer.new(:wint_t, 1024, true)
154
+
155
+ length = GetCurrentDirectoryW(path1.size, path1)
156
+
157
+ if length == 0 || length > path1.size
158
+ raise SystemCallError.new("GetCurrentDirectoryW", FFI.errno)
159
+ end
160
+
161
+ length = GetShortPathNameW(path1, path2, path2.size)
162
+
163
+ if length == 0 || length > path2.size
164
+ raise SystemCallError.new("GetShortPathNameW", FFI.errno)
165
+ end
166
+
167
+ length = GetLongPathNameW(path2, path3, path3.size)
168
+
169
+ if length == 0 || length > path3.size
170
+ raise SystemCallError.new("GetLongPathNameW", FFI.errno)
171
+ end
172
+
173
+ path = path3.read_bytes(length * 2).wstrip
174
+
175
+ begin
176
+ path.encode(Encoding.default_external)
177
+ rescue Encoding::UndefinedConversionError
178
+ path.encode('UTF-8')
179
+ end
180
+ end
181
+
182
+ alias :pwd :getwd
183
+ end
184
+ end
185
+
186
+ # Creates the symlink +to+, linked to the existing directory +from+. If the
187
+ # +to+ directory already exists, it must be empty or an error is raised.
188
+ #
189
+ # Example:
190
+ #
191
+ # Dir.mkdir('C:/from')
192
+ # Dir.create_junction('C:/to', 'C:/from')
193
+ #
194
+ def self.create_junction(to, from)
195
+ to = string_check(to).wincode
196
+ from = string_check(from).wincode
197
+
198
+ from_path = (0.chr * 1024).encode('UTF-16LE')
199
+
200
+ length = GetFullPathNameW(from, from_path.size, from_path, nil)
201
+
202
+ if length == 0 || length > from_path.size
203
+ raise SystemCallError.new("GetFullPathNameW", FFI.errno)
204
+ else
205
+ from_path.strip!
206
+ end
207
+
208
+ to_path = (0.chr * 1024).encode('UTF-16LE')
209
+
210
+ length = GetFullPathNameW(to, to_path.size, to_path, nil)
211
+
212
+ if length == 0 || length > to_path.size
213
+ raise SystemCallError.new("GetFullPathNameW", FFI.errno)
214
+ else
215
+ to_path.strip!
216
+ end
217
+
218
+ # You can create a junction to a directory that already exists, so
219
+ # long as it's empty.
220
+ unless CreateDirectoryW(to_path, nil)
221
+ if FFI.errno != ERROR_ALREADY_EXISTS
222
+ raise SystemCallError.new("CreateDirectoryW", FFI.errno)
223
+ end
224
+ end
225
+
226
+ begin
227
+ # Generic read & write + open existing + reparse point & backup semantics
228
+ handle = CreateFileW(
229
+ to_path,
230
+ GENERIC_READ | GENERIC_WRITE,
231
+ 0,
232
+ nil,
233
+ OPEN_EXISTING,
234
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
235
+ 0
236
+ )
237
+
238
+ if handle == INVALID_HANDLE_VALUE
239
+ raise SystemCallError.new("CreateFileW", FFI.errno)
240
+ end
241
+
242
+ target = "\\??\\".encode('UTF-16LE') + from_path
243
+
244
+ rdb = REPARSE_JDATA_BUFFER.new
245
+ rdb[:ReparseTag] = 2684354563 # IO_REPARSE_TAG_MOUNT_POINT
246
+ rdb[:ReparseDataLength] = target.bytesize + 12
247
+ rdb[:Reserved] = 0
248
+ rdb[:SubstituteNameOffset] = 0
249
+ rdb[:SubstituteNameLength] = target.bytesize
250
+ rdb[:PrintNameOffset] = target.bytesize + 2
251
+ rdb[:PrintNameLength] = 0
252
+ rdb[:PathBuffer] = target
253
+
254
+ bytes = FFI::MemoryPointer.new(:ulong)
255
+
256
+ begin
257
+ bool = DeviceIoControl(
258
+ handle,
259
+ CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, 0),
260
+ rdb,
261
+ rdb[:ReparseDataLength] + rdb.header_size,
262
+ nil,
263
+ 0,
264
+ bytes,
265
+ nil
266
+ )
267
+
268
+ error = FFI.errno
269
+
270
+ unless bool
271
+ RemoveDirectoryW(to_path)
272
+ raise SystemCallError.new("DeviceIoControl", error)
273
+ end
274
+ ensure
275
+ CloseHandle(handle)
276
+ end
277
+ end
278
+
279
+ self
280
+ end
281
+
282
+ # Returns the path that a given junction points to. Raises an
283
+ # Errno::ENOENT error if the given path does not exist. Returns false
284
+ # if it is not a junction.
285
+ #
286
+ # Example:
287
+ #
288
+ # Dir.mkdir('C:/from')
289
+ # Dir.create_junction('C:/to', 'C:/from')
290
+ # Dir.read_junction("c:/to") # => "c:/from"
291
+ #
292
+ def self.read_junction(junction)
293
+ return false unless Dir.junction?(junction)
294
+
295
+ junction = string_check(junction).wincode
296
+
297
+ junction_path = (0.chr * 1024).encode('UTF-16LE')
298
+
299
+ length = GetFullPathNameW(junction, junction_path.size, junction_path, nil)
300
+
301
+ if length == 0 || length > junction_path.size
302
+ raise SystemCallError.new("GetFullPathNameW", FFI.errno)
303
+ else
304
+ junction_path.strip!
305
+ end
306
+
307
+ begin
308
+ # Generic read & write + open existing + reparse point & backup semantics
309
+ handle = CreateFileW(
310
+ junction_path,
311
+ GENERIC_READ | GENERIC_WRITE,
312
+ 0,
313
+ nil,
314
+ OPEN_EXISTING,
315
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
316
+ 0
317
+ )
318
+
319
+ if handle == INVALID_HANDLE_VALUE
320
+ raise SystemCallError.new("CreateFileW", FFI.errno)
321
+ end
322
+
323
+ rdb = REPARSE_JDATA_BUFFER.new
324
+ rdb[:ReparseTag] = 0
325
+ rdb[:ReparseDataLength] = 0
326
+ rdb[:Reserved] = 0
327
+ rdb[:SubstituteNameOffset] = 0
328
+ rdb[:SubstituteNameLength] = 0
329
+ rdb[:PrintNameOffset] = 0
330
+ rdb[:PrintNameLength] = 0
331
+ rdb[:PathBuffer] = ''
332
+
333
+ bytes = FFI::MemoryPointer.new(:ulong)
334
+
335
+ begin
336
+ bool = DeviceIoControl(
337
+ handle,
338
+ CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, 0),
339
+ nil,
340
+ 0,
341
+ rdb,
342
+ 1024,
343
+ bytes,
344
+ nil
345
+ )
346
+
347
+ error = FFI.errno
348
+
349
+ unless bool
350
+ raise SystemCallError.new("DeviceIoControl", error)
351
+ end
352
+ ensure
353
+ CloseHandle(handle)
354
+ end
355
+ end
356
+
357
+ # MSDN says print and substitute names can be in any order
358
+ jname = (rdb[:PathBuffer].to_ptr + rdb[:SubstituteNameOffset]).read_string(rdb[:SubstituteNameLength])
359
+ jname = jname.bytes.to_a.pack('C*')
360
+ jname = jname.force_encoding("UTF-16LE")
361
+ raise "Invalid junction name: #{jname.encode('UTF-8')}" unless jname[0..3] == "\\??\\".encode("UTF-16LE")
362
+ begin
363
+ File.expand_path(jname[4..-1].encode(Encoding.default_external))
364
+ rescue Encoding::UndefinedConversionError
365
+ File.expand_path(jname[4..-1].encode('UTF-8'))
366
+ end
367
+ end
368
+
369
+ # Returns whether or not +path+ is empty. Returns false if +path+ is not
370
+ # a directory, or contains any files other than '.' or '..'.
371
+ #
372
+ def self.empty?(path)
373
+ PathIsDirectoryEmptyW("#{path}".wincode)
374
+ end
375
+
376
+ # Returns whether or not +path+ is a junction.
377
+ #
378
+ def self.junction?(path)
379
+ string_check(path)
380
+ bool = true
381
+
382
+ attrib = GetFileAttributesW("#{path}".wincode)
383
+
384
+ # Only directories with a reparse point attribute can be junctions
385
+ if attrib == INVALID_FILE_ATTRIBUTES ||
386
+ attrib & FILE_ATTRIBUTE_DIRECTORY == 0 ||
387
+ attrib & FILE_ATTRIBUTE_REPARSE_POINT == 0
388
+ then
389
+ bool = false
390
+ end
391
+
392
+ bool
393
+ end
394
+
395
+ # Class level aliases
396
+ #
397
+ class << self
398
+ alias reparse_dir? junction?
399
+ end
400
+
401
+ private
402
+
403
+ class << self
404
+ # Simulate MRI's contortions for a stringiness check.
405
+ def string_check(arg)
406
+ return arg if arg.is_a?(String)
407
+ return arg.send(:to_str) if arg.respond_to?(:to_str, true) # MRI honors it, even if private
408
+ return arg.to_path if arg.respond_to?(:to_path)
409
+ raise TypeError
410
+ end
411
+ end
412
+
413
+ # Macro from Windows header file, used by the create_junction method.
414
+ def self.CTL_CODE(device, function, method, access)
415
+ ((device) << 16) | ((access) << 14) | ((function) << 2) | (method)
416
+ end
417
+ end