win32-file-attributes 1.0.0

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/CHANGES ADDED
@@ -0,0 +1,2 @@
1
+ = 1.0.0 - 26-Nov-2012
2
+ * Initial release as an independent library.
@@ -0,0 +1,10 @@
1
+ * CHANGES
2
+ * MANIFEST
3
+ * Rakefile
4
+ * README
5
+ * win32-file-attributes.gemspec
6
+ * lib/win32/file/attributes.rb
7
+ * lib/win32/file/windows/constants.rb
8
+ * lib/win32/file/windows/functions.rb
9
+ * lib/win32/file/windows/structs.rb
10
+ * test/test_win32_file_attributes.rb
data/README ADDED
@@ -0,0 +1,46 @@
1
+ == Description
2
+ Additional methods for the File class that relate to file properties
3
+ on the Microsoft Windows operating system.
4
+
5
+ == Prerequisites
6
+ * ffi
7
+
8
+ == Installation
9
+ gem install win32-file-attributes
10
+
11
+ == Synopsis
12
+
13
+ require 'win32/file/attributes
14
+
15
+ p File.hidden?(file) # => false
16
+ p File.attributes(file) # => ['archive', 'indexed']
17
+
18
+ == Notes
19
+ If you have the win32-file gem already installed then you do not need this
20
+ gem. This library was split out from the win32-file gem in order to ease
21
+ testing and maintenance.
22
+
23
+ Otherwise, the only difference is that this library uses FFI instead
24
+ of win32-api. This also means that it works with JRuby.
25
+
26
+ == Known issues or bugs
27
+ None that I'm aware of.
28
+
29
+ Please report any issues you find on the github page at:
30
+
31
+ https://github.com/djberg96/win32-file-attributes/issues
32
+
33
+ == License
34
+ Artistic 2.0
35
+
36
+ == Copyright
37
+ (C) 2003-2012, Daniel J. Berger, All Rights Reserved
38
+
39
+ == Warranty
40
+ This package is provided "as is" and without any express or
41
+ implied warranties, including, without limitation, the implied
42
+ warranties of merchantability and fitness for a particular purpose.
43
+
44
+ == Authors
45
+ * Daniel J. Berger
46
+ * Park Heesob
@@ -0,0 +1,27 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/testtask'
4
+
5
+ CLEAN.include('**/*.gem', '**/*.rbc')
6
+
7
+ namespace :gem do
8
+ desc 'Build the win32-file-attributes gem'
9
+ task :create => [:clean] do
10
+ spec = eval(IO.read('win32-file-attributes.gemspec'))
11
+ Gem::Builder.new(spec).build
12
+ end
13
+
14
+ desc "Install the win32-file-attributes gem"
15
+ task :install => [:create] do
16
+ file = Dir["*.gem"].first
17
+ sh "gem install #{file}"
18
+ end
19
+ end
20
+
21
+ Rake::TestTask.new do |t|
22
+ task :test => :clean
23
+ t.warning = true
24
+ t.verbose = true
25
+ end
26
+
27
+ task :default => :test
@@ -0,0 +1,556 @@
1
+ require File.join(File.dirname(__FILE__), 'windows', 'constants')
2
+ require File.join(File.dirname(__FILE__), 'windows', 'structs')
3
+ require File.join(File.dirname(__FILE__), 'windows', 'functions')
4
+
5
+ class File
6
+ include Windows::File::Constants
7
+ include Windows::File::Functions
8
+ extend Windows::File::Constants
9
+ extend Windows::File::Structs
10
+ extend Windows::File::Functions
11
+
12
+ # The version of the win32-file library
13
+ WIN32_FILE_ATTRIBUTE_VERSION = '1.0.0'
14
+
15
+ ## ABBREVIATED ATTRIBUTE CONSTANTS
16
+
17
+ # The file or directory is an archive. Typically used to mark files for
18
+ # backup or removal.
19
+ ARCHIVE = FILE_ATTRIBUTE_ARCHIVE
20
+
21
+ # The file or directory is encrypted. For a file, this means that all
22
+ # data in the file is encrypted. For a directory, this means that
23
+ # encryption is # the default for newly created files and subdirectories.
24
+ COMPRESSED = FILE_ATTRIBUTE_COMPRESSED
25
+
26
+ # The file is hidden. Not included in an ordinary directory listing.
27
+ HIDDEN = FILE_ATTRIBUTE_HIDDEN
28
+
29
+ # A file that does not have any other attributes set.
30
+ NORMAL = FILE_ATTRIBUTE_NORMAL
31
+
32
+ # The data of a file is not immediately available. This attribute indicates
33
+ # that file data is physically moved to offline storage.
34
+ OFFLINE = FILE_ATTRIBUTE_OFFLINE
35
+
36
+ # The file is read only. Apps can read it, but not write to it or delete it.
37
+ READONLY = FILE_ATTRIBUTE_READONLY
38
+
39
+ # The file is part of or used exclusively by an operating system.
40
+ SYSTEM = FILE_ATTRIBUTE_SYSTEM
41
+
42
+ # The file is being used for temporary storage.
43
+ TEMPORARY = FILE_ATTRIBUTE_TEMPORARY
44
+
45
+ # The file or directory is to be indexed by the content indexing service.
46
+ # Note that we have inverted the traditional definition.
47
+ INDEXED = 0x0002000
48
+
49
+ # Synonym for File::INDEXED.
50
+ CONTENT_INDEXED = INDEXED
51
+
52
+ ## SINGLETON METHODS
53
+
54
+ # Returns an array of strings indicating the attributes for that file.
55
+ # The possible values are:
56
+ #
57
+ # archive
58
+ # compressed
59
+ # directory
60
+ # encrypted
61
+ # hidden
62
+ # indexed
63
+ # normal
64
+ # offline
65
+ # readonly
66
+ # reparse_point
67
+ # sparse
68
+ # system
69
+ # temporary
70
+ #
71
+ def self.attributes(file)
72
+ attributes = GetFileAttributesW(file.wincode)
73
+
74
+ if attributes == INVALID_FILE_ATTRIBUTES
75
+ raise SystemCallError.new("GetFileAttributes", FFI.errno)
76
+ end
77
+
78
+ arr = []
79
+
80
+ arr << 'archive' if attributes & FILE_ATTRIBUTE_ARCHIVE > 0
81
+ arr << 'compressed' if attributes & FILE_ATTRIBUTE_COMPRESSED > 0
82
+ arr << 'directory' if attributes & FILE_ATTRIBUTE_DIRECTORY > 0
83
+ arr << 'encrypted' if attributes & FILE_ATTRIBUTE_ENCRYPTED > 0
84
+ arr << 'hidden' if attributes & FILE_ATTRIBUTE_HIDDEN > 0
85
+ arr << 'indexed' if attributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED == 0
86
+ arr << 'normal' if attributes & FILE_ATTRIBUTE_NORMAL > 0
87
+ arr << 'offline' if attributes & FILE_ATTRIBUTE_OFFLINE > 0
88
+ arr << 'readonly' if attributes & FILE_ATTRIBUTE_READONLY > 0
89
+ arr << 'reparse_point' if attributes & FILE_ATTRIBUTE_REPARSE_POINT > 0
90
+ arr << 'sparse' if attributes & FILE_ATTRIBUTE_SPARSE_FILE > 0
91
+ arr << 'system' if attributes & FILE_ATTRIBUTE_SYSTEM > 0
92
+ arr << 'temporary' if attributes & FILE_ATTRIBUTE_TEMPORARY > 0
93
+
94
+ arr
95
+ end
96
+
97
+ # Sets the file attributes based on the given (numeric) +flags+. This does
98
+ # not remove existing attributes, it merely adds to them. Use the
99
+ # File.remove_attributes method if you want to remove them.
100
+ #
101
+ # Please not that certain attributes cannot always be applied. For example,
102
+ # you cannot convert a regular file into a directory. Common sense should
103
+ # guide you here.
104
+ #
105
+ def self.set_attributes(file, flags)
106
+ wfile = file.wincode
107
+ attributes = GetFileAttributesW(wfile)
108
+
109
+ if attributes == INVALID_FILE_ATTRIBUTES
110
+ raise SystemCallError.new("GetFileAttributes", FFI.errno)
111
+ end
112
+
113
+ attributes |= flags
114
+
115
+ if SetFileAttributesW(wfile, attributes) == 0
116
+ raise SystemCallError.new("SetFileAttributes", FFI.errno)
117
+ end
118
+
119
+ self
120
+ end
121
+
122
+ # Removes the file attributes based on the given (numeric) +flags+.
123
+ #
124
+ def self.remove_attributes(file, flags)
125
+ wfile = file.wincode
126
+ attributes = GetFileAttributesW(wfile)
127
+
128
+ if attributes == INVALID_FILE_ATTRIBUTES
129
+ raise SystemCallError.new("GetFileAttributes", FFI.errno)
130
+ end
131
+
132
+ attributes &= ~flags
133
+
134
+ if SetFileAttributesW(wfile, attributes) == 0
135
+ raise SystemCallError.new("SetFileAttributes", FFI.errno)
136
+ end
137
+
138
+ self
139
+ end
140
+
141
+ # Returns whether or not the file or directory is an archive file or
142
+ # directory. Applications typically use this attribute to mark files
143
+ # for backup or removal.
144
+ #
145
+ def self.archive?(file)
146
+ check_for_attribute(file, FILE_ATTRIBUTE_ARCHIVE)
147
+ end
148
+
149
+ # Returns whether or not the file or directory is compressed. For a file,
150
+ # this means that all of the data in the file is compressed. For a directory,
151
+ # this means that compression is the default for newly created files and
152
+ # subdirectories.
153
+ #
154
+ def self.compressed?(file)
155
+ check_for_attribute(file, FILE_ATTRIBUTE_COMPRESSED)
156
+ end
157
+
158
+ # Returns whether or not the file or directory is encrypted. For a file,
159
+ # this means that all data in the file is encrypted. For a directory, this
160
+ # means that encryption is the default for newly created files and
161
+ # subdirectories.
162
+ #
163
+ def self.encrypted?(file)
164
+ check_for_attribute(file, FILE_ATTRIBUTE_ENCRYPTED)
165
+ end
166
+
167
+ # Returns whether or not the file or directory is hidden. A hidden file
168
+ # does not show up in an ordinary directory listing.
169
+ #
170
+ def self.hidden?(file)
171
+ check_for_attribute(file, FILE_ATTRIBUTE_HIDDEN)
172
+ end
173
+
174
+ # Returns whether or not the file or directory has been indexed by
175
+ # the content indexing service.
176
+ #
177
+ def self.indexed?(file)
178
+ !check_for_attribute(file, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
179
+ end
180
+
181
+ # Returns whether or not the file is a normal file or directory. A normal
182
+ # file or directory does not have any other attributes set.
183
+ #
184
+ def self.normal?(file)
185
+ check_for_attribute(file, FILE_ATTRIBUTE_NORMAL)
186
+ end
187
+
188
+ # Returns whether or not the data of a file is available immediately.
189
+ # If true it indicates that the file data is physically moved to offline
190
+ # storage.
191
+ #
192
+ def self.offline?(file)
193
+ check_for_attribute(file, FILE_ATTRIBUTE_OFFLINE)
194
+ end
195
+
196
+ # Returns whether or not the file is read-only. If a file is read-only then
197
+ # applications can read the file, but cannot write to it or delete it.
198
+ #
199
+ # Note that this attribute is not honored on directories.
200
+ #
201
+ def self.readonly?(file)
202
+ check_for_attribute(file, FILE_ATTRIBUTE_READONLY)
203
+ end
204
+
205
+ # Returns true if the file or directory has an associated reparse point. A
206
+ # reparse point is a collection of user defined data associated with a file
207
+ # or directory. For more on reparse points, search
208
+ # http://msdn.microsoft.com.
209
+ #
210
+ def self.reparse_point?(file)
211
+ check_for_attribute(file, FILE_ATTRIBUTE_REPARSE_POINT)
212
+ end
213
+
214
+ # Returns whether or not the file is a sparse file. A sparse file is a
215
+ # file in which much of the data is zeros, typically image files.
216
+ #
217
+ def self.sparse?(file)
218
+ check_for_attribute(file, FILE_ATTRIBUTE_SPARSE_FILE)
219
+ end
220
+
221
+ # Returns whether or not the file or directory is a system file. A system
222
+ # file is a file that is part of the operating system or is used exclusively
223
+ # by the operating system.
224
+ #
225
+ def self.system?(file)
226
+ check_for_attribute(file, FILE_ATTRIBUTE_SYSTEM)
227
+ end
228
+
229
+ # Returns whether or not the file is being used for temporary storage.
230
+ #
231
+ # File systems avoid writing data back to mass storage if sufficient cache
232
+ # memory is available, because often the application deletes the temporary
233
+ # file shortly after the handle is closed. In that case, the system can
234
+ # entirely avoid writing the data. Otherwise, the data will be written after
235
+ # the handle is closed.
236
+ #
237
+ def self.temporary?(file)
238
+ check_for_attribute(file, FILE_ATTRIBUTE_TEMPORARY)
239
+ end
240
+
241
+ class << self
242
+ alias read_only? readonly?
243
+ alias content_indexed? indexed?
244
+ alias set_attr set_attributes
245
+ alias unset_attr remove_attributes
246
+ end
247
+
248
+ ## INSTANCE METHODS
249
+
250
+ # Sets whether or not the file is an archive file. Applications typically
251
+ # use this attribute to mark files for backup or removal.
252
+ #
253
+ def archive=(bool)
254
+ wide_path = self.path.wincode
255
+ attributes = GetFileAttributesW(wide_path)
256
+
257
+ if attributes == INVALID_FILE_ATTRIBUTES
258
+ raise SystemCallError.new("GetFileAttributes", FFI.errno)
259
+ end
260
+
261
+ if bool
262
+ attributes |= FILE_ATTRIBUTE_ARCHIVE;
263
+ else
264
+ attributes &= ~FILE_ATTRIBUTE_ARCHIVE;
265
+ end
266
+
267
+ if SetFileAttributesW(wide_path, attributes) == 0
268
+ raise SystemCallError.new("SetFileAttributes", FFI.errno)
269
+ end
270
+
271
+ self
272
+ end
273
+
274
+ # Sets whether or not the file is a compressed file. For a file, this means
275
+ # that all of the data in the file is compressed. For a directory, this means
276
+ # that compression is the default for newly created files and subdirectories.
277
+ #
278
+ def compressed=(bool)
279
+ in_buf = FFI::MemoryPointer.new(:ulong)
280
+ bytes = FFI::MemoryPointer.new(:ulong)
281
+
282
+ compression_value = bool ? COMPRESSION_FORMAT_DEFAULT : COMPRESSION_FORMAT_NONE
283
+ in_buf.write_ulong(compression_value)
284
+
285
+ # We can't use get_osfhandle here because we need specific attributes
286
+ handle = CreateFileW(
287
+ self.path.wincode,
288
+ FILE_READ_DATA | FILE_WRITE_DATA,
289
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
290
+ nil,
291
+ OPEN_EXISTING,
292
+ 0,
293
+ 0
294
+ )
295
+
296
+ if handle == INVALID_HANDLE_VALUE
297
+ raise SystemCallError.new("CreateFile", FFI.errno)
298
+ end
299
+
300
+ begin
301
+ bool = DeviceIoControl(
302
+ handle,
303
+ FSCTL_SET_COMPRESSION(),
304
+ in_buf,
305
+ in_buf.size,
306
+ nil,
307
+ 0,
308
+ bytes,
309
+ nil
310
+ )
311
+
312
+ unless bool
313
+ raise SystemCallError.new("DeviceIoControl", FFI.errno)
314
+ end
315
+ ensure
316
+ CloseHandle(handle)
317
+ end
318
+
319
+ self
320
+ end
321
+
322
+ # Sets the hidden attribute to true or false. Setting this attribute to
323
+ # true means that the file is not included in an ordinary directory listing.
324
+ #
325
+ def hidden=(bool)
326
+ wide_path = self.path.wincode
327
+ attributes = GetFileAttributesW(wide_path)
328
+
329
+ if attributes == INVALID_FILE_ATTRIBUTES
330
+ raise SystemCallError.new("GetFileAttributes", FFI.errno)
331
+ end
332
+
333
+ if bool
334
+ attributes |= FILE_ATTRIBUTE_HIDDEN;
335
+ else
336
+ attributes &= ~FILE_ATTRIBUTE_HIDDEN;
337
+ end
338
+
339
+ if SetFileAttributesW(wide_path, attributes) == 0
340
+ raise SystemCallError.new("SetFileAttributes", FFI.errno)
341
+ end
342
+
343
+ self
344
+ end
345
+
346
+ # Sets the 'indexed' attribute to true or false. Setting this to
347
+ # false means that the file will not be indexed by the content indexing
348
+ # service.
349
+ #
350
+ def indexed=(bool)
351
+ wide_path = self.path.wincode
352
+ attributes = GetFileAttributesW(wide_path)
353
+
354
+ if attributes == INVALID_FILE_ATTRIBUTES
355
+ raise SystemCallError.new("GetFileAttributes", FFI.errno)
356
+ end
357
+
358
+ if bool
359
+ attributes &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
360
+ else
361
+ attributes |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
362
+ end
363
+
364
+ if SetFileAttributesW(wide_path, attributes) == 0
365
+ raise SystemCallError.new("SetFileAttributes", FFI.errno)
366
+ end
367
+
368
+ self
369
+ end
370
+
371
+ alias :content_indexed= :indexed=
372
+
373
+ # Sets the normal attribute. Note that only 'true' is a valid argument,
374
+ # which has the effect of removing most other attributes. Attempting to
375
+ # pass any value except true will raise an ArgumentError.
376
+ #
377
+ def normal=(bool)
378
+ unless bool
379
+ raise ArgumentError, "only 'true' may be passed as an argument"
380
+ end
381
+
382
+ if SetFileAttributesW(self.path.wincode, FILE_ATTRIBUTE_NORMAL) == 0
383
+ raise SystemCallError.new("SetFileAttributes", FFI.errno)
384
+ end
385
+
386
+ self
387
+ end
388
+
389
+ # Sets whether or not a file is online or not. Setting this to false means
390
+ # that the data of the file is not immediately available. This attribute
391
+ # indicates that the file data has been physically moved to offline storage.
392
+ # This attribute is used by Remote Storage, the hierarchical storage
393
+ # management software.
394
+ #
395
+ # Applications should not arbitrarily change this attribute.
396
+ #
397
+ def offline=(bool)
398
+ wide_path = self.path.wincode
399
+ attributes = GetFileAttributesW(wide_path)
400
+
401
+ if attributes == INVALID_FILE_ATTRIBUTES
402
+ raise SystemCallError.new("GetFileAttributes", FFI.errno)
403
+ end
404
+
405
+ if bool
406
+ attributes |= FILE_ATTRIBUTE_OFFLINE;
407
+ else
408
+ attributes &= ~FILE_ATTRIBUTE_OFFLINE;
409
+ end
410
+
411
+ if SetFileAttributesW(wide_path, attributes) == 0
412
+ raise SystemCallError.new("SetFileAttributes", FFI.errno)
413
+ end
414
+
415
+ self
416
+ end
417
+
418
+ # Sets the readonly attribute. If set to true the the file or directory is
419
+ # readonly. Applications can read the file but cannot write to it or delete
420
+ # it. In the case of a directory, applications cannot delete it.
421
+ #
422
+ def readonly=(bool)
423
+ wide_path = self.path.wincode
424
+ attributes = GetFileAttributesW(wide_path)
425
+
426
+ if attributes == INVALID_FILE_ATTRIBUTES
427
+ raise SystemCallError.new("GetFileAttributes", FFI.errno)
428
+ end
429
+
430
+ if bool
431
+ attributes |= FILE_ATTRIBUTE_READONLY;
432
+ else
433
+ attributes &= ~FILE_ATTRIBUTE_READONLY;
434
+ end
435
+
436
+ if SetFileAttributesW(wide_path, attributes) == 0
437
+ raise SystemCallError.new("SetFileAttributes", FFI.errno)
438
+ end
439
+
440
+ self
441
+ end
442
+
443
+ # Sets the file to a sparse (usually image) file. Note that you cannot
444
+ # remove the sparse property from a file.
445
+ #
446
+ def sparse=(bool)
447
+ unless bool
448
+ warn 'cannot remove sparse property from a file - operation ignored'
449
+ return
450
+ end
451
+
452
+ bytes = FFI::MemoryPointer.new(:ulong)
453
+
454
+ handle = CreateFileW(
455
+ self.path.wincode,
456
+ FILE_READ_DATA | FILE_WRITE_DATA,
457
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
458
+ 0,
459
+ OPEN_EXISTING,
460
+ FSCTL_SET_SPARSE(),
461
+ 0
462
+ )
463
+
464
+ if handle == INVALID_HANDLE_VALUE
465
+ raise SystemCallError.new("CreateFile", FFI.errno)
466
+ end
467
+
468
+ begin
469
+ bool = DeviceIoControl(
470
+ handle,
471
+ FSCTL_SET_SPARSE(),
472
+ nil,
473
+ 0,
474
+ nil,
475
+ 0,
476
+ bytes,
477
+ nil
478
+ )
479
+
480
+ unless bool == 0
481
+ raise SystemCallError.new("DeviceIoControl", FFI.errno)
482
+ end
483
+ ensure
484
+ CloseHandle(handle)
485
+ end
486
+
487
+ self
488
+ end
489
+
490
+ # Set whether or not the file is a system file. A system file is a file
491
+ # that is part of the operating system or is used exclusively by it.
492
+ #
493
+ def system=(bool)
494
+ wide_path = self.path.wincode
495
+ attributes = GetFileAttributesW(wide_path)
496
+
497
+ if attributes == INVALID_FILE_ATTRIBUTES
498
+ raise SystemCallError.new("GetFileAttributes", FFI.errno)
499
+ end
500
+
501
+ if bool
502
+ attributes |= FILE_ATTRIBUTE_SYSTEM;
503
+ else
504
+ attributes &= ~FILE_ATTRIBUTE_SYSTEM;
505
+ end
506
+
507
+ if SetFileAttributesW(wide_path, attributes) == 0
508
+ raise SystemCallError.new("SetFileAttributes", FFI.errno)
509
+ end
510
+
511
+ self
512
+ end
513
+
514
+ # Sets whether or not the file is being used for temporary storage.
515
+ #
516
+ # File systems avoid writing data back to mass storage if sufficient cache
517
+ # memory is available, because often the application deletes the temporary
518
+ # file shortly after the handle is closed. In that case, the system can
519
+ # entirely avoid writing the data. Otherwise, the data will be written
520
+ # after the handle is closed.
521
+ #
522
+ def temporary=(bool)
523
+ wide_path = self.path.wincode
524
+ attributes = GetFileAttributesW(wide_path)
525
+
526
+ if attributes == INVALID_FILE_ATTRIBUTES
527
+ raise SystemCallError.new("GetFileAttributes", FFI.errno)
528
+ end
529
+
530
+ if bool
531
+ attributes |= FILE_ATTRIBUTE_TEMPORARY;
532
+ else
533
+ attributes &= ~FILE_ATTRIBUTE_TEMPORARY;
534
+ end
535
+
536
+ if SetFileAttributesW(wide_path, attributes) == 0
537
+ raise SystemCallError.new("SetFileAttributes", FFI.errno)
538
+ end
539
+
540
+ self
541
+ end
542
+
543
+ private
544
+
545
+ # Convenience method used internally for the various boolean singleton methods.
546
+ #
547
+ def self.check_for_attribute(file, attribute)
548
+ attributes = GetFileAttributesW(file.wincode)
549
+
550
+ if attributes == INVALID_FILE_ATTRIBUTES
551
+ raise SystemCallError.new("GetFileAttributes", FFI.errno)
552
+ end
553
+
554
+ attributes & attribute > 0 ? true : false
555
+ end
556
+ end
@@ -0,0 +1,33 @@
1
+ module Windows
2
+ module File
3
+ module Constants
4
+ FILE_ATTRIBUTE_READONLY = 0x00000001
5
+ FILE_ATTRIBUTE_HIDDEN = 0x00000002
6
+ FILE_ATTRIBUTE_SYSTEM = 0x00000004
7
+ FILE_ATTRIBUTE_DIRECTORY = 0x00000010
8
+ FILE_ATTRIBUTE_ARCHIVE = 0x00000020
9
+ FILE_ATTRIBUTE_ENCRYPTED = 0x00000040
10
+ FILE_ATTRIBUTE_NORMAL = 0x00000080
11
+ FILE_ATTRIBUTE_TEMPORARY = 0x00000100
12
+ FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200
13
+ FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400
14
+ FILE_ATTRIBUTE_COMPRESSED = 0x00000800
15
+ FILE_ATTRIBUTE_OFFLINE = 0x00001000
16
+
17
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000
18
+
19
+ COMPRESSION_FORMAT_NONE = 0
20
+ COMPRESSION_FORMAT_DEFAULT = 1
21
+
22
+ INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
23
+ INVALID_HANDLE_VALUE = 0xFFFFFFFF
24
+
25
+ FILE_READ_DATA = 1
26
+ FILE_WRITE_DATA = 2
27
+ FILE_SHARE_READ = 1
28
+ FILE_SHARE_WRITE = 2
29
+
30
+ OPEN_EXISTING = 3
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,36 @@
1
+ require 'ffi'
2
+
3
+ module Windows
4
+ module File
5
+ module Functions
6
+ extend FFI::Library
7
+ ffi_lib :kernel32
8
+
9
+ attach_function :CloseHandle, [:ulong], :bool
10
+ attach_function :CreateFileW, [:buffer_in, :ulong, :ulong, :pointer, :ulong, :ulong, :ulong], :ulong
11
+ attach_function :DeviceIoControl, [:ulong, :ulong, :pointer, :ulong, :pointer, :ulong, :pointer, :pointer], :bool
12
+ attach_function :GetFileAttributesW, [:buffer_in], :ulong
13
+ attach_function :SetFileAttributesW, [:buffer_in, :ulong], :ulong
14
+
15
+ def CTL_CODE(device, function, method, access)
16
+ ((device) << 16) | ((access) << 14) | ((function) << 2) | (method)
17
+ end
18
+
19
+ def FSCTL_SET_COMPRESSION
20
+ CTL_CODE(9, 16, 0, 3)
21
+ end
22
+
23
+ def FSCTL_SET_SPARSE
24
+ CTL_CODE(9, 49, 0, 0)
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ class String
31
+ # Convenience method for converting strings to UTF-16LE for wide character
32
+ # functions that require it.
33
+ def wincode
34
+ (self.tr(File::SEPARATOR, File::ALT_SEPARATOR) + 0.chr).encode('UTF-16LE')
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+ module Windows
2
+ module File
3
+ module Structs
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,376 @@
1
+ #############################################################################
2
+ # test_win32_file_attributes.rb
3
+ #
4
+ # Test case for the attribute related methods of win32-file. You should run
5
+ # this via the 'rake test' or 'rake test_attributes' task.
6
+ #############################################################################
7
+ require 'ffi'
8
+ require 'test-unit'
9
+ require 'win32/file/attributes'
10
+
11
+ class TC_Win32_File_Attributes < Test::Unit::TestCase
12
+ extend FFI::Library
13
+ ffi_lib :kernel32
14
+
15
+ attach_function :GetFileAttributes, :GetFileAttributesA, [:string], :ulong
16
+ attach_function :SetFileAttributes, :SetFileAttributesA, [:string, :ulong], :ulong
17
+
18
+ def self.startup
19
+ Dir.chdir(File.dirname(File.expand_path(File.basename(__FILE__))))
20
+ @@file = File.join(Dir.pwd, 'test_file.txt')
21
+ File.open(@@file, 'w'){ |fh| fh.puts "This is a test." }
22
+ end
23
+
24
+ def setup
25
+ @fh = File.open(@@file)
26
+ @attr = GetFileAttributes(@@file)
27
+ end
28
+
29
+ test "version is set to expected value" do
30
+ assert_equal('1.0.0', File::WIN32_FILE_ATTRIBUTE_VERSION)
31
+ end
32
+
33
+ test "temporary? singleton method basic functionality" do
34
+ assert_respond_to(File, :temporary?)
35
+ assert_nothing_raised{ File.temporary?(@@file) }
36
+ end
37
+
38
+ test "temporary? singleton method returns expected value" do
39
+ assert_false(File.temporary?(@@file))
40
+ end
41
+
42
+ test "temporary? singleton method requires a single argument" do
43
+ assert_raises(ArgumentError){ File.temporary? }
44
+ assert_raises(ArgumentError){ File.temporary?(@@file, 'foo') }
45
+ end
46
+
47
+ test "temporary? instance method basic functionality" do
48
+ assert_respond_to(@fh, :temporary=)
49
+ assert_nothing_raised{ @fh.temporary = true }
50
+ end
51
+
52
+ test "temporary? instance method works as expected" do
53
+ assert_false(File.temporary?(@@file))
54
+ @fh.temporary = true
55
+ assert_true(File.temporary?(@@file))
56
+ end
57
+
58
+ test "system? singleton method basic functionality" do
59
+ assert_respond_to(File, :system?)
60
+ assert_nothing_raised{ File.system?(@@file) }
61
+ end
62
+
63
+ test "system? singleton method returns the expected value" do
64
+ assert_false(File.system?(@@file))
65
+ end
66
+
67
+ test "system singleton method requires a single argument" do
68
+ assert_raises(ArgumentError){ File.system? }
69
+ assert_raises(ArgumentError){ File.system?(@@file, 'foo') }
70
+ end
71
+
72
+ test "system instance method basic functionality" do
73
+ assert_respond_to(@fh, :system=)
74
+ assert_nothing_raised{ @fh.system = true }
75
+ end
76
+
77
+ test "system instance method works as expected" do
78
+ assert_false(File.system?(@@file))
79
+ @fh.system = true
80
+ assert_true(File.system?(@@file))
81
+ end
82
+
83
+ test "sparse? singleton method basic functionality" do
84
+ assert_respond_to(File, :sparse?)
85
+ assert_nothing_raised{ File.sparse?(@@file) }
86
+ end
87
+
88
+ test "sparse? singleton method returns expected value" do
89
+ assert_false(File.sparse?(@@file))
90
+ end
91
+
92
+ test "sparse? singleton method requires one argument" do
93
+ assert_raises(ArgumentError){ File.sparse? }
94
+ assert_raises(ArgumentError){ File.sparse?(@@file, 'foo') }
95
+ end
96
+
97
+ # I don't actually test true assignment here since making a file a
98
+ # sparse file can't be undone.
99
+ test "sparse? instance method basic functionality" do
100
+ assert_respond_to(@fh, :sparse=)
101
+ assert_nothing_raised{ @fh.sparse= false }
102
+ end
103
+
104
+ test "reparse_point? singleton method basic functionality" do
105
+ assert_respond_to(File, :reparse_point?)
106
+ assert_nothing_raised{ File.reparse_point?(@@file) }
107
+ end
108
+
109
+ test "reparse_point? singleton method returns the expected value" do
110
+ assert_false(File.reparse_point?(@@file))
111
+ end
112
+
113
+ test "reparse_point? singleton method requires a single argument" do
114
+ assert_raises(ArgumentError){ File.reparse_point? }
115
+ assert_raises(ArgumentError){ File.reparse_point?(@@file, 'foo') }
116
+ end
117
+
118
+ test "readonly? singleton method basic functionality" do
119
+ assert_respond_to(File, :readonly?)
120
+ assert_nothing_raised{ File.readonly?(@@file) }
121
+ end
122
+
123
+ test "readonly? singleton method returns expected result" do
124
+ assert_false(File.readonly?(@@file))
125
+ end
126
+
127
+ test "readonly? singleton method requires a single argument" do
128
+ assert_raises(ArgumentError){ File.read_only? }
129
+ assert_raises(ArgumentError){ File.read_only?(@@file, 'foo') }
130
+ end
131
+
132
+ test "read_only? is an alias for readonly?" do
133
+ assert_respond_to(File, :read_only?)
134
+ assert_alias_method(File, :read_only?, :readonly?)
135
+ end
136
+
137
+ test "readonly? instance method basic functionality" do
138
+ assert_respond_to(@fh, :readonly=)
139
+ assert_nothing_raised{ @fh.readonly = true }
140
+ end
141
+
142
+ test "readonly? instance method returns expected value" do
143
+ assert_false(File.readonly?(@@file))
144
+ @fh.readonly = true
145
+ assert_true(File.readonly?(@@file))
146
+ end
147
+
148
+ test "offline? singleton method basic functionality" do
149
+ assert_respond_to(File, :offline?)
150
+ assert_nothing_raised{ File.offline?(@@file) }
151
+ end
152
+
153
+ test "offline? singleton method returns expected result" do
154
+ assert_false(File.offline?(@@file))
155
+ end
156
+
157
+ test "offline? singleton method requires a single argument" do
158
+ assert_raises(ArgumentError){ File.offline? }
159
+ assert_raises(ArgumentError){ File.offline?(@@file, 'foo') }
160
+ end
161
+
162
+ test "offline? instance method basic functionality" do
163
+ assert_respond_to(@fh, :offline=)
164
+ assert_nothing_raised{ @fh.offline = true }
165
+ end
166
+
167
+ test "offline? instance method returns expected value" do
168
+ assert_false(File.offline?(@@file))
169
+ @fh.offline = true
170
+ assert_true(File.offline?(@@file))
171
+ end
172
+
173
+ test "normal? singleton method basic functionality" do
174
+ assert_respond_to(File, :normal?)
175
+ assert_nothing_raised{ File.normal?(@@file) }
176
+ end
177
+
178
+ test "normal? singleton method returns expected results" do
179
+ assert_false(File.normal?(@@file))
180
+ @fh.normal = true
181
+ assert_true(File.normal?(@@file))
182
+ end
183
+
184
+ test "normal? singleton method requires a single argument" do
185
+ assert_raises(ArgumentError){ File.normal? }
186
+ assert_raises(ArgumentError){ File.normal?(@@file, 'foo') }
187
+ end
188
+
189
+ test "normal? instance method basic functionality" do
190
+ assert_respond_to(@fh, :normal=)
191
+ assert_nothing_raised{ @fh.normal = true }
192
+ end
193
+
194
+ test "normal? instance method setter does not accept false" do
195
+ assert_raises(ArgumentError){ @fh.normal = false }
196
+ end
197
+
198
+ test "hidden? singleton method basic functionality" do
199
+ assert_respond_to(File, :hidden?)
200
+ assert_nothing_raised{ File.hidden?(@@file) }
201
+ end
202
+
203
+ test "hidden? singleton method returns the expected result" do
204
+ assert_false(File.hidden?(@@file))
205
+ @fh.hidden = true
206
+ assert_true(File.hidden?(@@file))
207
+ end
208
+
209
+ test "hidden? singleton method requires a single argument" do
210
+ assert_raises(ArgumentError){ File.hidden? }
211
+ assert_raises(ArgumentError){ File.hidden?(@@file, 'foo') }
212
+ end
213
+
214
+ test "hidden? instance method basic functionality" do
215
+ assert_respond_to(@fh, :hidden=)
216
+ assert_nothing_raised{ @fh.hidden = true }
217
+ end
218
+
219
+ test "encrypted? singleton method basic functionality" do
220
+ assert_respond_to(File, :encrypted?)
221
+ assert_nothing_raised{ File.encrypted?(@@file) }
222
+ end
223
+
224
+ test "encrypted? singleton method returns the expected result" do
225
+ assert_false(File.encrypted?(@@file))
226
+ end
227
+
228
+ test "encrypted? singleton method requires a single argument" do
229
+ assert_raises(ArgumentError){ File.encrypted? }
230
+ assert_raises(ArgumentError){ File.encrypted?(@@file, 'foo') }
231
+ end
232
+
233
+ test "indexed? singleton method basic functionality" do
234
+ assert_respond_to(File, :indexed?)
235
+ assert_nothing_raised{ File.indexed?(@@file) }
236
+ end
237
+
238
+ test "indexed? singleton method returns the expected results" do
239
+ assert_true(File.indexed?(@@file))
240
+ @fh.indexed = false
241
+ assert_false(File.indexed?(@@file))
242
+ end
243
+
244
+ test "content_indexed? is an alias for indexed?" do
245
+ assert_respond_to(File, :content_indexed?)
246
+ assert_alias_method(File, :content_indexed?, :indexed?)
247
+ end
248
+
249
+ test "indexed? singleton method requires a single argument" do
250
+ assert_raises(ArgumentError){ File.indexed? }
251
+ assert_raises(ArgumentError){ File.indexed?(@@file, 'foo') }
252
+ end
253
+
254
+ test "indexed? instance method basic functionality" do
255
+ assert_respond_to(@fh, :indexed=)
256
+ assert_nothing_raised{ @fh.indexed = true }
257
+ end
258
+
259
+ test "indexed? instance method returns expected method" do
260
+ assert_true(File.indexed?(@@file))
261
+ @fh.indexed = false
262
+ assert_false(File.indexed?(@@file))
263
+ end
264
+
265
+ test "compressed? singleton method basic functionality" do
266
+ assert_respond_to(File, :compressed?)
267
+ assert_nothing_raised{ File.compressed?(@@file) }
268
+ end
269
+
270
+ test "compressed? singleton method returns the expected result" do
271
+ assert_false(File.compressed?(@@file))
272
+ end
273
+
274
+ test "compressed instance method setter basic functionality" do
275
+ assert_respond_to(@fh, :compressed=)
276
+ assert_false(File.compressed?(@@file))
277
+ end
278
+
279
+ test "compressed? singleton method requires a single argument" do
280
+ assert_raises(ArgumentError){ File.compressed? }
281
+ assert_raises(ArgumentError){ File.compressed?(@@file, 'foo') }
282
+ end
283
+
284
+ # We have to explicitly reset the compressed attribute to false as
285
+ # the last of these assertions.
286
+
287
+ test "compressed instance method setter works as expected" do
288
+ assert_nothing_raised{ @fh.compressed = true }
289
+ assert_true(File.compressed?(@@file))
290
+ assert_nothing_raised{ @fh.compressed = false }
291
+ assert_false(File.compressed?(@@file))
292
+ end
293
+
294
+ test "archive? singleton method basic functionality" do
295
+ assert_respond_to(File, :archive?)
296
+ assert_nothing_raised{ File.archive?(@@file) }
297
+ end
298
+
299
+ test "archive? singleton method returns the expected results" do
300
+ assert_true(File.archive?(@@file))
301
+ @fh.archive = false
302
+ assert_false(File.archive?(@@file))
303
+ end
304
+
305
+ test "archive? singleton method requires a single argument" do
306
+ assert_raises(ArgumentError){ File.archive? }
307
+ assert_raises(ArgumentError){ File.archive?(@@file, 'foo') }
308
+ end
309
+
310
+ test "archive instance method setter basic functionality" do
311
+ assert_respond_to(@fh, :archive=)
312
+ assert_nothing_raised{ @fh.archive = false }
313
+ end
314
+
315
+ test "attributes singleton method basic functionality" do
316
+ assert_respond_to(File, :attributes)
317
+ assert_kind_of(Array, File.attributes(@@file))
318
+ end
319
+
320
+ test "attributes singleton method returns expected results" do
321
+ assert_equal(['archive', 'indexed'], File.attributes(@@file))
322
+ end
323
+
324
+ test "set_attributes singleton method basic functionality" do
325
+ assert_respond_to(File, :set_attributes)
326
+ assert_nothing_raised{ File.set_attributes(@@file, File::FILE_ATTRIBUTE_HIDDEN) }
327
+ end
328
+
329
+ test "set_attributes singleton method works as expected" do
330
+ assert_nothing_raised{ File.set_attributes(@@file, File::FILE_ATTRIBUTE_HIDDEN) }
331
+ assert_true(File.attributes(@@file).include?('hidden'))
332
+ end
333
+
334
+ test "set_attr is an alias for set_attributes" do
335
+ assert_respond_to(File, :set_attr)
336
+ assert_alias_method(File, :set_attr, :set_attributes)
337
+ end
338
+
339
+ test "remove_attributes singleton method basic functionality" do
340
+ assert_respond_to(File, :remove_attributes)
341
+ assert_nothing_raised{ File.remove_attributes(@@file, File::FILE_ATTRIBUTE_ARCHIVE) }
342
+ end
343
+
344
+ test "remove_attributes works as expected" do
345
+ assert_true(File.archive?(@@file))
346
+ assert_nothing_raised{ File.remove_attributes(@@file, File::FILE_ATTRIBUTE_ARCHIVE) }
347
+ assert_false(File.archive?(@@file))
348
+ end
349
+
350
+ test "unset_attr is an alias for remove_attributes" do
351
+ assert_respond_to(File, :unset_attr)
352
+ assert_alias_method(File, :unset_attr, :remove_attributes)
353
+ end
354
+
355
+ test "shorthand constants are defined" do
356
+ assert_not_nil(File::ARCHIVE)
357
+ assert_not_nil(File::HIDDEN)
358
+ assert_not_nil(File::NORMAL)
359
+ assert_not_nil(File::INDEXED)
360
+ assert_not_nil(File::OFFLINE)
361
+ assert_not_nil(File::READONLY)
362
+ assert_not_nil(File::SYSTEM)
363
+ assert_not_nil(File::TEMPORARY)
364
+ assert_not_nil(File::CONTENT_INDEXED) # alias for INDEXED
365
+ end
366
+
367
+ def teardown
368
+ SetFileAttributes(@@file, @attr)
369
+ @fh.close
370
+ end
371
+
372
+ def self.shutdown
373
+ File.delete(@@file)
374
+ @@file = nil
375
+ end
376
+ end
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'win32-file-attributes'
5
+ spec.version = '1.0.0'
6
+ spec.authors = ['Daniel J. Berger', 'Park Heesob']
7
+ spec.license = 'Artistic 2.0'
8
+ spec.email = 'djberg96@gmail.com'
9
+ spec.homepage = 'http://github.com/djberg96/win32-file-attributes'
10
+ spec.summary = 'File attribute methods for the File class on MS Windows'
11
+ spec.test_files = Dir['test/test*']
12
+ spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
13
+
14
+ spec.rubyforge_project = 'win32utils'
15
+ spec.extra_rdoc_files = ['README', 'CHANGES', 'MANIFEST']
16
+
17
+ spec.add_dependency('ffi')
18
+ spec.add_development_dependency('test-unit')
19
+
20
+ spec.description = <<-EOF
21
+ The win32-file-attribute library adds several file attribute methods to
22
+ the core File class which are specific to MS Windows.
23
+ EOF
24
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: win32-file-attributes
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Daniel J. Berger
9
+ - Park Heesob
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-11-26 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ffi
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: test-unit
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ description: ! " The win32-file-attribute library adds several file attribute methods
48
+ to\n the core File class which are specific to MS Windows.\n"
49
+ email: djberg96@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files:
53
+ - README
54
+ - CHANGES
55
+ - MANIFEST
56
+ files:
57
+ - CHANGES
58
+ - lib/win32/file/attributes.rb
59
+ - lib/win32/file/windows/constants.rb
60
+ - lib/win32/file/windows/functions.rb
61
+ - lib/win32/file/windows/structs.rb
62
+ - MANIFEST
63
+ - Rakefile
64
+ - README
65
+ - test/test_win32_file_attributes.rb
66
+ - win32-file-attributes.gemspec
67
+ homepage: http://github.com/djberg96/win32-file-attributes
68
+ licenses:
69
+ - Artistic 2.0
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project: win32utils
88
+ rubygems_version: 1.8.24
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: File attribute methods for the File class on MS Windows
92
+ test_files:
93
+ - test/test_win32_file_attributes.rb