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 +2 -0
- data/MANIFEST +10 -0
- data/README +46 -0
- data/Rakefile +27 -0
- data/lib/win32/file/attributes.rb +556 -0
- data/lib/win32/file/windows/constants.rb +33 -0
- data/lib/win32/file/windows/functions.rb +36 -0
- data/lib/win32/file/windows/structs.rb +6 -0
- data/test/test_win32_file_attributes.rb +376 -0
- data/win32-file-attributes.gemspec +24 -0
- metadata +93 -0
data/CHANGES
ADDED
data/MANIFEST
ADDED
@@ -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
|
data/Rakefile
ADDED
@@ -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,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
|