win32-dir 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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