/[eiffelstudio]/branches/eth/eve/Src/library/base/elks/kernel/directory.e
ViewVC logotype

Contents of /branches/eth/eve/Src/library/base/elks/kernel/directory.e

Parent Directory Parent Directory | Revision Log Revision Log


Revision 92964 - (show annotations)
Fri Sep 20 05:41:23 2013 UTC (6 years ago) by jasonw
File size: 20740 byte(s)
<<Merged from trunk#92963.>>
1 note
2 description: "Directories, in the Unix sense, with creation and exploration features"
3 library: "Free implementation of ELKS library"
4 status: "See notice at end of class."
5 legal: "See notice at end of class."
6 date: "$Date$"
7 revision: "$Revision$"
8
9 class DIRECTORY
10
11 inherit
12 DISPOSABLE
13
14 NATIVE_STRING_HANDLER
15
16 create
17 make, make_with_path, make_with_name,
18 make_open_read
19
20 feature -- Initialization
21
22 make (dn: READABLE_STRING_GENERAL)
23 -- Create directory object for directory
24 -- of name `dn'.
25 require
26 string_exists: dn /= Void
27 do
28 make_with_name (dn)
29 ensure
30 name_set: internal_name = dn
31 end
32
33 make_with_name (dn: READABLE_STRING_GENERAL)
34 -- Create directory object for directory
35 -- of name `dn'.
36 require
37 string_exists: dn /= Void
38 do
39 set_name (dn)
40 mode := Close_directory
41 ensure
42 name_set: internal_name = dn
43 end
44
45 make_with_path (a_path: PATH)
46 -- Create file object with `a_path' as path.
47 require
48 a_path_attached: a_path /= Void
49 do
50 make (a_path.name)
51 end
52
53 make_open_read (dn: READABLE_STRING_GENERAL)
54 -- Create directory object for directory
55 -- of name `dn' and open it for reading.
56 require
57 string_exists: dn /= Void
58 do
59 make (dn)
60 open_read
61 ensure
62 name_set: internal_name = dn
63 end
64
65 feature -- Creation
66
67 create_dir
68 -- Create a physical directory.
69 require
70 physical_not_exists: not exists
71 do
72 file_mkdir (internal_name_pointer.item)
73 end
74
75 recursive_create_dir
76 -- Create the directory recursively.
77 --
78 -- Ex: if /temp/ exists but not /temp/test, then trying
79 -- to create /temp/test/toto will create /temp/test
80 -- and then /temp/test/toto.
81 local
82 l_directory: DIRECTORY
83 l_directories_to_build: ARRAYED_LIST [PATH]
84 l_path: PATH
85 l_entry: detachable PATH
86 l_io_exception: IO_FAILURE
87 do
88 -- First ensure the path is fully resolved.
89 l_path := path.canonical_path
90 create l_directory.make_with_path (l_path)
91 if not l_directory.exists then
92 -- Directory does not exist, we need to create it.
93 -- Find the first existing directory in `l_path'
94 from
95 create l_directories_to_build.make (10)
96 l_entry := l_path.entry
97 until
98 l_directory.exists or l_entry = Void
99 loop
100 l_directories_to_build.extend (l_path)
101 l_path := l_path.parent
102 l_entry := l_path.entry
103 l_directory.make_with_path (l_path)
104 end
105
106 -- Recursively create the directory.
107 l_directories_to_build.finish
108 from
109 -- Make sure we start from somewhere. If `l_path' is Void,
110 -- it means we were trying to create a path without a root such as "abc/def".
111 if l_path = Void then
112 create l_path.make_empty
113 end
114 until
115 l_directories_to_build.before
116 loop
117 l_path := l_directories_to_build.item
118 l_directories_to_build.back
119
120 l_directory.make_with_path (l_path)
121 l_directory.create_dir
122 if not l_directory.exists then
123 create l_io_exception
124 l_io_exception.set_description ({STRING_32} "Cannot create: " + l_path.name)
125 l_io_exception.raise
126 end
127 end
128 end
129 ensure
130 physical_exists: exists
131 end
132
133 feature -- Access
134
135 path: PATH
136 -- Associated path of Current.
137 do
138 create Result.make_from_pointer (internal_name_pointer.item)
139 ensure
140 entry_not_empty: not Result.is_empty
141 end
142
143 readentry
144 -- Read next directory entry
145 -- make result available in `lastentry'.
146 -- Make result Void if all entries have been read.
147 require
148 is_opened: not is_closed
149 do
150 last_entry_pointer := eif_dir_next (directory_pointer)
151 if last_entry_pointer /= default_pointer then
152 -- For backward compatibility, we had to leave `lastentry' as an attribute
153 -- which is being updated at each `readentry' call.
154 lastentry := file_info.pointer_to_file_name_8 (last_entry_pointer)
155 else
156 -- We reached the end of our iteration.
157 lastentry := Void
158 end
159 end
160
161 name: STRING_8
162 -- File name as a STRING_8 instance. The value might be truncated
163 -- from the original name used to create the current FILE instance.
164 obsolete
165 "Use `path' to ensure you can retrieve all kind of names."
166 do
167 Result := internal_name.as_string_8
168 ensure then
169 name_not_empty: not Result.is_empty
170 end
171
172 has_entry (entry_name: READABLE_STRING_GENERAL): BOOLEAN
173 -- Has directory the entry `entry_name'?
174 --| The use of `dir_temp' is required not
175 --| to change the position in the current
176 --| directory entries list.
177 require
178 string_exists: entry_name /= Void
179 local
180 dir_temp: DIRECTORY
181 e: like last_entry_pointer
182 do
183 create dir_temp.make_open_read (internal_name)
184 from
185 dir_temp.start
186 dir_temp.readentry
187 e := dir_temp.last_entry_pointer
188 until
189 Result or e = default_pointer
190 loop
191 if attached {READABLE_STRING_8} entry_name then
192 -- We were given a STRING_8 instance, we lookup the name using the old way
193 Result := entry_name.same_string (file_info.pointer_to_file_name_8 (e))
194 elseif attached file_info.pointer_to_file_name_32 (e) as l_str then
195 Result := entry_name.same_string (l_str)
196 end
197 dir_temp.readentry
198 e := dir_temp.last_entry_pointer
199 end
200 dir_temp.close
201 end
202
203 open_read
204 -- Open directory for reading.
205 do
206 directory_pointer := dir_open (internal_name_pointer.item)
207 mode := Read_directory
208 end
209
210 close
211 -- Close directory.
212 require
213 is_open: not is_closed
214 do
215 dir_close (directory_pointer)
216 directory_pointer := default_pointer
217 mode := Close_directory
218 end
219
220 start
221 -- Go to first entry of directory.
222 require
223 is_opened: not is_closed
224 do
225 directory_pointer := dir_rewind (directory_pointer, internal_name_pointer.item)
226 end
227
228 change_name (new_name: READABLE_STRING_GENERAL)
229 -- Change directory name to `new_name'.
230 require
231 new_name_not_void: new_name /= Void
232 directory_exists: exists
233 local
234 l_ptr: MANAGED_POINTER
235 do
236 l_ptr := file_info.file_name_to_pointer (new_name, Void)
237 eif_dir_rename (internal_name_pointer.item, l_ptr.item)
238 set_name (new_name)
239 ensure
240 name_changed: internal_name = new_name
241 end
242
243 rename_path (new_name: PATH)
244 -- Change directory name to `new_name'.
245 require
246 new_name_not_void: new_name /= Void
247 new_name_not_empty: not new_name.is_empty
248 file_exists: exists
249 local
250 l_ptr: MANAGED_POINTER
251 do
252 l_ptr := new_name.to_pointer
253 eif_dir_rename (internal_name_pointer.item, l_ptr.item)
254 set_name (new_name.name)
255 ensure
256 name_changed: internal_name = new_name.name
257 end
258
259 feature -- Measurement
260
261 count: INTEGER
262 -- Number of entries in directory.
263 require
264 directory_exists: exists
265 local
266 dir_temp: DIRECTORY
267 counter: INTEGER
268 do
269 create dir_temp.make_open_read (internal_name)
270 from
271 dir_temp.start
272 dir_temp.readentry
273 until
274 dir_temp.last_entry_pointer = default_pointer
275 loop
276 counter := counter + 1
277 dir_temp.readentry
278 end
279 Result := counter
280 dir_temp.close
281 end
282
283 feature -- Conversion
284
285 entries: ARRAYED_LIST [PATH]
286 -- The entries, in sequential format, in a platform specific order.
287 local
288 dir_temp: DIRECTORY
289 e: like last_entry_pointer
290 do
291 create dir_temp.make_open_read (internal_name)
292 -- Arbitrary size for arrayed_list creation to avoid
293 -- querying `count' which traverses list of entries
294 -- in current directory as we do here, making current
295 -- less efficient if Current has a lot of entries.
296 create Result.make (16)
297 from
298 dir_temp.start
299 dir_temp.readentry
300 e := dir_temp.last_entry_pointer
301 until
302 e = default_pointer
303 loop
304 Result.extend (create {PATH}.make_from_pointer (e))
305 dir_temp.readentry
306 e := dir_temp.last_entry_pointer
307 end
308 dir_temp.close
309 end
310
311 linear_representation: ARRAYED_LIST [STRING_8]
312 -- The entries, in sequential format.
313 -- Use `entries' or `linear_representation_32' to get a readable version
314 -- of the Unicode entries.
315 obsolete
316 "Use `entries' instead if your application is using Unicode file names."
317 local
318 dir_temp: DIRECTORY
319 e: like last_entry_pointer
320 do
321 create dir_temp.make_open_read (internal_name)
322 -- Arbitrary size for arrayed_list creation to avoid
323 -- querying `count' which traverses list of entries
324 -- in current directory as we do here, making current
325 -- less efficient if Current has a lot of entries.
326 create Result.make (16)
327 from
328 dir_temp.start
329 dir_temp.readentry
330 e := dir_temp.last_entry_pointer
331 until
332 e = default_pointer
333 loop
334 Result.extend (file_info.pointer_to_file_name_8 (e))
335 dir_temp.readentry
336 e := dir_temp.last_entry_pointer
337 end
338 dir_temp.close
339 end
340
341 linear_representation_32: ARRAYED_LIST [STRING_32]
342 -- The entries, in sequential format.
343 local
344 dir_temp: DIRECTORY
345 e: like last_entry_pointer
346 l_path: PATH
347 do
348 create dir_temp.make_open_read (internal_name)
349 -- Arbitrary size for arrayed_list creation to avoid
350 -- querying `count' which traverses list of entries
351 -- in current directory as we do here, making current
352 -- less efficient if Current has a lot of entries.
353 create Result.make (16)
354 from
355 dir_temp.start
356 dir_temp.readentry
357 e := dir_temp.last_entry_pointer
358 until
359 e = default_pointer
360 loop
361 create l_path.make_from_pointer (e)
362 Result.extend (create {STRING_32}.make_from_string (l_path.name))
363 dir_temp.readentry
364 e := dir_temp.last_entry_pointer
365 end
366 dir_temp.close
367 end
368
369 feature -- Status report
370
371 last_entry_32: detachable STRING_32
372 -- Last Unicode entry read by `readentry' if any.
373 do
374 if last_entry_pointer /= default_pointer then
375 Result := file_info.pointer_to_file_name_32 (last_entry_pointer)
376 end
377 end
378
379 last_entry_8: detachable STRING_8
380 -- Raw byte sequence of the last found entry if this entry cannot be
381 -- expressed with Unicode characters. This is useful
382 -- when handling a file that is not a valid UTF-8 sequence on Unix.
383 do
384 if last_entry_pointer /= default_pointer then
385 Result := file_info.pointer_to_file_name_8 (last_entry_pointer)
386 end
387 end
388
389 lastentry: detachable STRING_8
390 -- Last entry read by `readentry'.
391 obsolete
392 "Use `last_entry_32' for Unicode file names, or `last_entry_8' otherwise."
393 attribute
394 end
395
396 is_closed: BOOLEAN
397 -- Is current directory closed?
398 do
399 Result := mode = Close_directory
400 end
401
402 is_empty: BOOLEAN
403 -- Is directory empty?
404 require
405 directory_exists: exists
406 do
407 -- count = 2, since there are "." and ".." which
408 -- are symbolic representations but not effective directories.
409 Result := (count = 2)
410 end
411
412 exists: BOOLEAN
413 -- Does the directory exist?
414 do
415 Result := eif_dir_exists (internal_name_pointer.item)
416 end
417
418 is_readable: BOOLEAN
419 -- Is the directory readable?
420 require
421 directory_exists: exists
422 do
423 Result := eif_dir_is_readable (internal_name_pointer.item)
424 end
425
426 is_executable: BOOLEAN
427 -- Is the directory executable?
428 require
429 directory_exists: exists
430 do
431 Result := eif_dir_is_executable (internal_name_pointer.item)
432 end
433
434 is_writable: BOOLEAN
435 -- Is the directory writable?
436 require
437 directory_exists: exists
438 do
439 Result := eif_dir_is_writable (internal_name_pointer.item)
440 end
441
442 feature -- Removal
443
444 delete
445 -- Delete directory if empty.
446 require
447 directory_exists: exists
448 empty_directory: is_empty
449 do
450 eif_dir_delete (internal_name_pointer.item)
451 end
452
453 delete_content
454 -- Delete all files located in current directory and its
455 -- subdirectories.
456 require
457 directory_exists: exists
458 do
459 delete_content_with_action (Void, Void, 0)
460 end
461
462 recursive_delete
463 -- Delete directory, its files and its subdirectories.
464 require
465 directory_exists: exists
466 do
467 delete_content
468 if is_empty then
469 delete
470 end
471 end
472
473 delete_content_with_action (
474 action: detachable PROCEDURE [ANY, TUPLE [LIST [READABLE_STRING_GENERAL]]]
475 is_cancel_requested: detachable FUNCTION [ANY, TUPLE, BOOLEAN]
476 file_number: INTEGER)
477 -- Delete all files located in current directory and its
478 -- subdirectories.
479 --
480 -- `action' is called each time `file_number' files has
481 -- been deleted and before the function exits. If `a_file_number'
482 -- is non-positive, nothing is done. If `a_file_number' is greater than
483 -- 1024, we limit its value to 1024.
484 -- `action' may be set to Void if you don't need it.
485 --
486 -- Same for `is_cancel_requested'.
487 -- Make it return `True' to cancel the operation.
488 -- `is_cancel_requested' may be set to Void if you don't need it.
489 require
490 directory_exists: exists
491 valid_file_number: file_number >= 0
492 local
493 l_path, l_file_name: PATH
494 file: detachable RAW_FILE
495 l_info: like file_info
496 dir: detachable DIRECTORY
497 dir_temp: DIRECTORY
498 l_last_entry_pointer: like last_entry_pointer
499 l_name: detachable STRING_8
500 file_count: INTEGER
501 deleted_files: ARRAYED_LIST [READABLE_STRING_32]
502 requested_cancel: BOOLEAN
503 do
504 file_count := 1
505 -- We limit `file_number' to something reasonable.
506 create deleted_files.make (file_number.min (1024))
507
508 from
509 -- To delete files we do not need to follow symbolic links.
510 l_info := file_info
511 l_info.set_is_following_symlinks (False)
512 -- Create a new directory that we will use to list all of its content.
513 create dir_temp.make_open_read (internal_name)
514 dir_temp.start
515 dir_temp.readentry
516 l_last_entry_pointer := dir_temp.last_entry_pointer
517 l_path := path
518 until
519 l_last_entry_pointer = default_pointer or requested_cancel
520 loop
521 -- Ignore current and parent directories.
522 l_name := l_info.pointer_to_file_name_8 (l_last_entry_pointer)
523 if not l_name.same_string (current_directory_string) and not l_name.same_string (parent_directory_string) then
524 -- Avoid creating too many objects.
525 l_file_name := path.extended (l_name)
526 l_info.update (l_file_name.name)
527 if l_info.exists then
528 if not l_info.is_symlink and then l_info.is_directory then
529 -- Start the recursion for true directory, we do not follow links to delete their content.
530 if dir /= Void then
531 dir.make_with_path (l_file_name)
532 else
533 create dir.make_with_path (l_file_name)
534 end
535 dir.recursive_delete_with_action (action, is_cancel_requested, file_number)
536 elseif l_info.is_writable then
537 if file /= Void then
538 file.reset_path (l_file_name)
539 else
540 create file.make_with_path (l_file_name)
541 end
542 file.delete
543 end
544
545 -- Add the name of the deleted file to our array
546 -- of deleted files.
547 deleted_files.extend (l_file_name.name)
548 file_count := file_count + 1
549
550 -- If `file_number' has been reached, call `action'.
551 if file_number > 0 and then file_count > file_number then
552 if action /= Void then
553 action.call ([deleted_files])
554 end
555 if is_cancel_requested /= Void then
556 requested_cancel := is_cancel_requested.item (Void)
557 end
558 deleted_files.wipe_out
559 file_count := 1
560 end
561 end
562 end
563 dir_temp.readentry
564 l_last_entry_pointer := dir_temp.last_entry_pointer
565 end
566 dir_temp.close
567
568 -- If there is more than one deleted file and no
569 -- agent has been called, call one now.
570 if file_count > 1 and action /= Void then
571 action.call ([deleted_files])
572 end
573 end
574
575 recursive_delete_with_action (
576 action: detachable PROCEDURE [ANY, TUPLE [LIST [READABLE_STRING_GENERAL]]]
577 is_cancel_requested: detachable FUNCTION [ANY, TUPLE, BOOLEAN]
578 file_number: INTEGER)
579 -- Delete directory, its files and its subdirectories.
580 --
581 -- `action' is called each time `file_number' files has
582 -- been deleted and before the function exits.
583 require
584 directory_exists: exists
585 local
586 deleted_files: ARRAYED_LIST [READABLE_STRING_GENERAL]
587 do
588 delete_content_with_action (action, is_cancel_requested, file_number)
589 if (is_cancel_requested = Void) or else (not is_cancel_requested.item (Void)) then
590 delete
591 -- Call the agent with the name of the directory
592 if action /= Void then
593 create deleted_files.make (1)
594 deleted_files.extend (internal_name)
595 action.call ([deleted_files])
596 end
597 end
598 end
599
600 dispose
601 -- Ensure this medium is closed when garbage collected.
602 do
603 if not is_closed then
604 close
605 end
606 end
607
608 feature {DIRECTORY} -- Implementation
609
610 directory_pointer: POINTER
611 -- Directory pointer as required in C
612
613 last_entry_pointer: POINTER
614 -- Pointer to the underlying C memory representing the last directory entry.
615
616 feature {NONE} -- Implementation
617
618 set_name (a_name: READABLE_STRING_GENERAL)
619 -- Set `name' with `a_name'.
620 do
621 internal_name := a_name
622 internal_detachable_name_pointer := file_info.file_name_to_pointer (a_name, internal_detachable_name_pointer)
623 ensure
624 name_set: internal_name = a_name
625 end
626
627 internal_name: READABLE_STRING_GENERAL
628 -- Store the name of the file as it was given to us by the user
629 -- to avoid conversion on storing as it is not necessary.
630
631 internal_name_pointer: MANAGED_POINTER
632 -- File system specific encoding of `internal_name'.
633 -- Typically a UTF-16 sequence on Windows, a UTF-8 sequence on Unix.
634 do
635 if attached internal_detachable_name_pointer as l_ptr then
636 Result := l_ptr
637 else
638 -- This is never True because `internal_detachable_name_pointer' is set during the creation
639 -- of the FILE object.
640 check internal_name_pointer_set: False then end
641 end
642 end
643
644 internal_detachable_name_pointer: detachable MANAGED_POINTER note option: stable attribute end
645 -- File system specific encoding of `internal_name'.
646 -- Typically a UTF-16 sequence on Windows, a UTF-8 sequence on Unix.
647
648 mode: INTEGER
649 -- Status mode of the directory.
650 -- Possible values are the following:
651
652 Close_directory: INTEGER = 1
653
654 Read_directory: INTEGER = 2
655
656 current_directory_string: STRING = "."
657 parent_directory_string: STRING = ".."
658 -- Constants to represent current (".") and parent ("..") directory.
659
660 directory_separator_string: STRING
661 -- Constant representing the directory separator
662 once
663 create Result.make (1)
664 Result.append_character (operating_environment.directory_separator)
665 end
666
667 file_info: FILE_INFO
668 -- To avoid creating objects when querying for files.
669 once
670 create Result.make
671 end
672
673 file_mkdir (dir_name: POINTER)
674 -- Make directory `dir_name'.
675 external
676 "C signature (EIF_FILENAME) use %"eif_file.h%""
677 alias
678 "eif_file_mkdir"
679 end
680
681 dir_open (dir_name: POINTER): POINTER
682 -- Open the directory `dir_name'.
683 external
684 "C signature (EIF_FILENAME): EIF_POINTER use %"eif_dir.h%""
685 alias
686 "eif_dir_open"
687 end
688
689 dir_rewind (dir_ptr: POINTER; dir_name: POINTER): POINTER
690 -- Rewind the directory `dir_ptr' with name `a_name' and return a new directory traversal pointer.
691 external
692 "C signature (EIF_POINTER, EIF_FILENAME): EIF_POINTER use %"eif_dir.h%""
693 alias
694 "eif_dir_rewind"
695 end
696
697 dir_close (dir_ptr: POINTER)
698 -- Close the directory `dir_ptr'.
699 external
700 "C use %"eif_dir.h%""
701 alias
702 "eif_dir_close"
703 end
704
705 eif_dir_next (dir_ptr: POINTER): POINTER
706 -- Return pointer to the next entry in the current iteration.
707 external
708 "C use %"eif_dir.h%""
709 end
710
711 eif_dir_delete (dir_name: POINTER)
712 -- Delete the directory `dir_name'.
713 external
714 "C signature (EIF_FILENAME) use %"eif_file.h%""
715 alias
716 "eif_file_unlink"
717 end
718
719 eif_dir_exists (dir_name: POINTER): BOOLEAN
720 -- Does the directory `dir_name' exist?
721 external
722 "C signature (EIF_FILENAME): EIF_BOOLEAN use %"eif_dir.h%""
723 end
724
725 eif_dir_is_readable (dir_name: POINTER): BOOLEAN
726 -- Is `dir_name' readable?
727 external
728 "C signature (EIF_FILENAME): EIF_BOOLEAN use %"eif_dir.h%""
729 end
730
731 eif_dir_is_executable (dir_name: POINTER): BOOLEAN
732 -- Is `dir_name' executable?
733 external
734 "C signature (EIF_FILENAME): EIF_BOOLEAN use %"eif_dir.h%""
735 end
736
737 eif_dir_is_writable (dir_name: POINTER): BOOLEAN
738 -- Is `dir_name' writable?
739 external
740 "C signature (EIF_FILENAME): EIF_BOOLEAN use %"eif_dir.h%""
741 end
742
743 eif_dir_rename (old_name, new_name: POINTER)
744 -- Change directory name from `old_name' to `new_name'.
745 external
746 "C signature (EIF_FILENAME, EIF_FILENAME) use %"eif_file.h%""
747 alias
748 "eif_file_rename"
749 end
750
751 invariant
752 name_attached: attached internal_name
753
754 note
755 copyright: "Copyright (c) 1984-2013, Eiffel Software and others"
756 license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
757 source: "[
758 Eiffel Software
759 5949 Hollister Ave., Goleta, CA 93117 USA
760 Telephone 805-685-1006, Fax 805-685-6869
761 Website http://www.eiffel.com
762 Customer support http://support.eiffel.com
763 ]"
764 end

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

  ViewVC Help
Powered by ViewVC 1.1.23