win32-file-attributes 1.0.0

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