View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    WWW:           http://www.swi-prolog.org
    4    Copyright (c)  2021, SWI-Prolog Solutions b.v.
    5    All rights reserved.
    6
    7    Redistribution and use in source and binary forms, with or without
    8    modification, are permitted provided that the following conditions
    9    are met:
   10
   11    1. Redistributions of source code must retain the above copyright
   12       notice, this list of conditions and the following disclaimer.
   13
   14    2. Redistributions in binary form must reproduce the above copyright
   15       notice, this list of conditions and the following disclaimer in
   16       the documentation and/or other materials provided with the
   17       distribution.
   18
   19    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   20    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   21    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   22    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   23    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   24    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   25    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   26    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   27    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   28    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   29    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   30    POSSIBILITY OF SUCH DAMAGE.
   31*/
   32
   33:- module(file_systems,
   34	  [ rename_file/2,		% +OldName, +NewName
   35	    rename_directory/2,		% +OldName, +NewName
   36
   37	    delete_file/1,		% +OldName
   38	    delete_directory/1,		% +Directory
   39	    delete_directory/2,		% +Directory, +Options
   40
   41	    directory_exists/1,		% +Directory
   42	    directory_exists/2,		% +Directory, +Mode
   43
   44	    make_directory/1,		% +Directory
   45
   46	    file_exists/1,		% +File
   47	    file_exists/2,		% +File, +Mode
   48	    file_must_exist/1,		% +File
   49	    file_must_exist/2,		% +File, +Mode
   50	    directory_must_exist/1,	% +File
   51	    directory_must_exist/2,	% +File, +Mode
   52
   53	    directory_member_of_directory/2, % -BaseName, -FullName
   54	    directory_member_of_directory/3, % +Directory, -BaseName, -FullName
   55	    directory_member_of_directory/4, % +Directory, +Pattern, -BaseName, -FullName
   56	    file_member_of_directory/2,	% -BaseName, -FullName
   57	    file_member_of_directory/3,	% +Directory, -BaseName, -FullName
   58	    file_member_of_directory/4,	% +Directory, +Pattern, -BaseName, -FullName
   59	    directory_members_of_directory/1, % -Set
   60	    directory_members_of_directory/2, % +Directory, -Set
   61	    directory_members_of_directory/3, % +Directory, +Pattern, -Set
   62	    file_members_of_directory/1, % -Set
   63	    file_members_of_directory/2, % +Directory, -Set
   64	    file_members_of_directory/3, % +Directory, +Pattern, -Set
   65
   66	    directory_property/2,	% +Directory, ?Property
   67	    directory_property/3,	% +Directory, ?Property, ?Value
   68	    file_property/2,		% +File, ?Property
   69	    file_property/3,		% +File, ?Property, ?Value
   70
   71	    current_directory/1,	% -Directory
   72	    current_directory/2		% -Directory, +NewDirectory
   73	  ]).   74:- use_module(library(filesex), [delete_directory_and_contents/1, directory_member/3, set_time_file/3]).   75:- use_module(library(lists), [member/2]).   76:- use_module(system, [datime/2]).   77
   78/** <module> SICStus 4 library(file_systems).
   79
   80@tbd	This library is incomplete.
   81	As of SICStus 4.6.0, the following predicates are missing:
   82
   83	* close_all_streams/0
   84
   85	Some predicates don't fully support all options available on SICStus.
   86	See the documentation for individual predicates for details.
   87
   88	The file access modes `execute` and `search` are interpreted
   89	slightly differently on SICStus and SWI. On SWI, `execute` and
   90	`search` are equivalent - both can be used with regular files
   91	and directories and will check execute or search permission
   92	depending on the file type, not the mode atom.
   93
   94	SICStus on the other hand checks the access modes only if the
   95	file in question has the appropriate type. Checking access mode
   96	`execute` on a directory or `search` on a regular file is
   97	equivalent to checking `exist`.
   98
   99	This difference affects not just file_exists/2 and
  100	directory_exists/2 in this library, but also the
  101	built-in absolute_file_name/3 with the option
  102	access(Mode).
  103
  104	On the other hand, file_property/2 and
  105	directory_property/2 with properties `executable` and
  106	`searchable` are *not* affected - here the emulation
  107	matches the native SICStus behavior.
  108
  109@see	https://sicstus.sics.se/sicstus/docs/4.6.0/html/sicstus.html/lib_002dfile_005fsystems.html
  110*/
  111
  112% Note: Unlike most of SWI's built-in file system predicates,
  113% SICStus library(file_systems) draws a strict distinction between files and directories.
  114% This means that "file" predicates will operate only on regular files -
  115% directories must be manipulated using the corresponding "directory" predicates instead.
  116% Because of this,
  117% some of SWI's built-in file predicates
  118% (that can operate on both files and directories)
  119% need to be replaced with versions that throw an error when receiving a non-regular file.
  120
  121
  122%!	rename_file(+OldName, +NewName) is det.
  123%
  124%	Like SWI's built-in rename_file/2, but only works on regular
  125%	files. To rename directories, rename_directory/2 must be used.
  126
  127rename_file(OldName, NewName) :-
  128	file_must_exist(OldName),
  129	system:rename_file(OldName, NewName).
  130
  131%!	rename_directory(+OldName, +NewName) is det.
  132%
  133%	Like SWI's built-in rename_file/2, but only works on
  134%	directories. To rename regular files, rename_file/2 must be used.
  135
  136rename_directory(OldName, NewName) :-
  137	directory_must_exist(OldName),
  138	system:rename_file(OldName, NewName).
  139
  140
  141%!	delete_file(+OldName) is det.
  142%
  143%	Like SWI's built-in delete_file/1, but only works on regular
  144%	files. To delete directories, delete_directory/1 must be used.
  145
  146delete_file(OldName) :-
  147	file_must_exist(OldName),
  148	system:delete_file(OldName).
  149
  150% SWI's built-in delete_directory/1 behaves like the one from SICStus library(file_systems).
  151
  152directory_not_empty_error(error(permission_error(delete, directory, _), _)).
  153
  154%!	delete_directory(+OldName, +Options) is semidet.
  155%
  156%	Extended verison of delete_directory/1. The only available
  157%	option is `if_nonempty(Value)`, which controls the behavior
  158%	when OldName is not empty. Value may be `ignore` (silently
  159%	succeed without deleting anything), `fail` (silently fail
  160%	without deleting anything), `error` (throw an error - default
  161%	behavior), and `delete` (recursively delete the directory and
  162%	its contents, as if by delete_directory_and_contents/1 from
  163%	library(filesex)).
  164
  165delete_directory(OldName, []) :- !, delete_directory(OldName).
  166delete_directory(OldName, [if_nonempty(ignore)]) :- !,
  167	catch(delete_directory(OldName), E,
  168	      (directory_not_empty_error(E) -> true ; throw(E))).
  169delete_directory(OldName, [if_nonempty(fail)]) :- !,
  170	catch(delete_directory(OldName), E,
  171	      (directory_not_empty_error(E) -> fail ; throw(E))).
  172delete_directory(OldName, [if_nonempty(error)]) :- !, delete_directory(OldName).
  173delete_directory(OldName, [if_nonempty(delete)]) :- !, delete_directory_and_contents(OldName).
  174
  175
  176%!	directory_exists(+Directory) is semidet.
  177%!	directory_exists(+Directory, +Mode) is semidet.
  178%
  179%	True if a directory exists at path Directory and can be accessed
  180%	according to Mode (defaults to `exist`). Accepts the same access
  181%	modes as absolute_file_name/3's `access` option.
  182
  183directory_exists(Directory) :- exists_directory(Directory).
  184directory_exists(Directory, Mode) :-
  185	% According to the SICStus 4.6 docs, this is "more or less equivalent".
  186	absolute_file_name(Directory, _, [file_type(directory), access(Mode), file_errors(fail)]).
  187
  188% SWI's built-in make_directory/1 behaves like the one from SICStus library(file_systems).
  189
  190%!	file_exists(+File) is semidet.
  191%!	file_exists(+File, +Mode) is semidet.
  192%
  193%	True if a regular file exists at path File and can be accessed
  194%	according to Mode (defaults to `exist`). Accepts the same access
  195%	modes as absolute_file_name/3's `access` option.
  196
  197file_exists(File) :- exists_file(File).
  198file_exists(File, Mode) :-
  199	% According to the SICStus 4.6 docs, this is "more or less equivalent".
  200	absolute_file_name(File, _, [access(Mode), file_errors(fail)]).
  201
  202%!	file_must_exist(+File) is det.
  203%!	file_must_exist(+File, +Mode) is det.
  204%
  205%	Ensure that a regular file exists at path File and can be
  206%	accessed according to Mode (defaults to `exist`). Otherwise an
  207%	exception is thrown. Accepts the same access modes as
  208%	absolute_file_name/3's `access` option.
  209
  210file_must_exist(File) :- file_must_exist(File, exist).
  211file_must_exist(File, Mode) :-
  212	% According to the SICStus 4.6 docs, this is "more or less equivalent".
  213	absolute_file_name(File, _, [access(Mode), file_errors(error)]).
  214
  215%!	directory_must_exist(+Directory) is det.
  216%!	directory_must_exist(+Directory, +Mode) is det.
  217%
  218%	Ensure that a directory exists at path Directory and can be
  219%	accessed according to Mode (defaults to `exist`). Otherwise an
  220%	exception is thrown. Accepts the same access modes as
  221%	absolute_file_name/3's `access` option.
  222
  223directory_must_exist(Directory) :- directory_must_exist(Directory, exist).
  224directory_must_exist(Directory, Mode) :-
  225	% According to the SICStus 4.6 docs, this is "more or less equivalent".
  226	absolute_file_name(Directory, _, [file_type(directory), access(Mode), file_errors(error)]).
  227
  228
  229member_of_directory_internal(Directory, BaseName, FullName, Options) :-
  230	absolute_file_name(Directory, AbsDirectory, [file_type(directory)]),
  231	directory_member(AbsDirectory, FullName, Options),
  232	file_base_name(FullName, BaseName).
  233
  234%!	directory_member_of_directory(-BaseName, -FullName) is nondet.
  235%!	directory_member_of_directory(+Directory, -BaseName, -FullName) is nondet.
  236%!	directory_member_of_directory(+Directory, +Pattern, -BaseName, -FullName) is nondet.
  237%!	file_member_of_directory(-BaseName, -FullName) is nondet.
  238%!	file_member_of_directory(+Directory, -BaseName, -FullName) is nondet.
  239%!	file_member_of_directory(+Directory, +Pattern, -BaseName, -FullName) is nondet.
  240%
  241%	True if Directory contains a directory or regular file
  242%	(respectively) named BaseName and the file's absolute path is
  243%	FullName. If Directory is not given, it defaults to the current
  244%	working directory. If Pattern is given, only succeeds if
  245%	BaseName also matches that glob pattern.
  246%
  247%	These predicates enumerate all matching files on backtracking.
  248%	This is also the intended usage pattern. For checking if a
  249%	specific file/directory exists, or to get its absolute path,
  250%	it's better to use file_exists/1, directory_exists/1, or
  251%	absolute_file_name/3.
  252
  253directory_member_of_directory(BaseName, FullName) :-
  254	directory_member_of_directory((.), BaseName, FullName).
  255directory_member_of_directory(Directory, BaseName, FullName) :-
  256	member_of_directory_internal(Directory, BaseName, FullName, [file_type(directory)]).
  257directory_member_of_directory(Directory, Pattern, BaseName, FullName) :-
  258	member_of_directory_internal(Directory, BaseName, FullName, [file_type(directory), matches(Pattern)]).
  259
  260file_member_of_directory(BaseName, FullName) :-
  261	file_member_of_directory((.), BaseName, FullName).
  262file_member_of_directory(Directory, BaseName, FullName) :-
  263	member_of_directory_internal(Directory, BaseName, FullName, []),
  264	% directory_member/3 has no option for filtering out directories...
  265	\+ directory_exists(FullName).
  266file_member_of_directory(Directory, Pattern, BaseName, FullName) :-
  267	member_of_directory_internal(Directory, BaseName, FullName, [matches(Pattern)]),
  268	% directory_member/3 has no option for filtering out directories...
  269	\+ directory_exists(FullName).
  270
  271%!	directory_members_of_directory(-Set) is det.
  272%!	directory_members_of_directory(+Directory, -Set) is det.
  273%!	directory_members_of_directory(+Directory, +Pattern, -Set) is det.
  274%!	file_members_of_directory(-Set) is det.
  275%!	file_members_of_directory(+Directory, -Set) is det.
  276%!	file_members_of_directory(+Directory, +Pattern, -Set) is det.
  277%
  278%	Unifies Set with a set of BaseName-FullName entries for all
  279%	directories or regular files (respectively) in Directory. If
  280%	Directory is not given, it defaults to the current working
  281%	directory. If Pattern is given, Set only includes entries where
  282%	BaseName matches that glob pattern.
  283
  284directory_members_of_directory(Set) :-
  285	directory_members_of_directory((.), Set).
  286directory_members_of_directory(Directory, Set) :-
  287	findall(BaseName-FullName, directory_member_of_directory(Directory, BaseName, FullName), Set).
  288directory_members_of_directory(Directory, Pattern, Set) :-
  289	findall(BaseName-FullName, directory_member_of_directory(Directory, Pattern, BaseName, FullName), Set).
  290
  291file_members_of_directory(Set) :-
  292	file_members_of_directory((.), Set).
  293file_members_of_directory(Directory, Set) :-
  294	findall(BaseName-FullName, file_member_of_directory(Directory, BaseName, FullName), Set).
  295file_members_of_directory(Directory, Pattern, Set) :-
  296	findall(BaseName-FullName, file_member_of_directory(Directory, Pattern, BaseName, FullName), Set).
  297
  298
  299%!	file_property(+Path, ?Property) is semidet.
  300%!	file_property(+Path, ?Property, -Value) is semidet.
  301%!	directory_property(+Path, ?Property) is semidet.
  302%!	directory_property(+Path, ?Property, -Value) is semidet.
  303%
  304%	True if a regular file or directory (respectively) exists at
  305%	Path and it has the given property and value. Property may be
  306%	unbound to backtrack over all available properties. If the Value
  307%	parameter is omitted, succeeds if Property has value `true`.
  308%
  309%	The following properties are currently supported:
  310%
  311%	* create_timestamp
  312%	* modify_timestamp
  313%	* access_timestamp
  314%	The file/directory's creation/modification/access time as a Unix
  315%	timestamp (as returned by SWI's set_time_file/3).
  316%	* create_localtime
  317%	* modify_localtime
  318%	* access_localtime
  319%	The file/directory's creation/modification/access time as a
  320%	datime/6 term (as returned by datime/2 from SICStus
  321%	library(system)).
  322%	* readable
  323%	* writable
  324%	* executable
  325%	* searchable
  326%	`true` or `false` depending on whether the file/directory is
  327%	readable/writable/executable/searchable. `executable` is only
  328%	supported on regular files and `searchable` only on directories.
  329%	* size_in_bytes
  330%	The file's size in bytes. Not supported on directories.
  331%
  332%	On Unix systems, `create_timestamp`/`create_localtime` don't
  333%	return the file's actual creation time, but rather its "ctime"
  334%	or "metadata change time". This matches the behavior of
  335%	SICStus 4.6.0.
  336%
  337%	As of SICStus 4.6.0, the following properties are not yet
  338%	emulated:
  339%
  340%	* set_user_id
  341%	* set_group_id
  342%	* save_text
  343%	* who_can_read
  344%	* who_can_write
  345%	* who_can_execute
  346%	* who_can_search
  347%	* owner_user_id
  348%	* owner_group_id
  349%	* owner_user_name
  350%	* owner_group_name
  351
  352time_property_name(create_timestamp).
  353time_property_name(modify_timestamp).
  354time_property_name(access_timestamp).
  355time_property_name(create_localtime).
  356time_property_name(modify_localtime).
  357time_property_name(access_localtime).
  358
  359time_property(create_timestamp, Ctime, _, _, Timestamp) :-
  360	Timestamp is integer(Ctime).
  361time_property(modify_timestamp, _, Mtime, _, Timestamp) :-
  362	Timestamp is integer(Mtime).
  363time_property(access_timestamp, _, _, Atime, Timestamp) :-
  364	Timestamp is integer(Atime).
  365time_property(create_localtime, Ctime, _, _, Datime) :-
  366	datime(Ctime, Datime).
  367time_property(modify_localtime, _, Mtime, _, Datime) :-
  368	datime(Mtime, Datime).
  369time_property(access_localtime, _, _, Atime, Datime) :-
  370	datime(Atime, Datime).
  371
  372% Properties that are available and implemented identically for files and directories.
  373file_or_directory_property(FileOrDirectory, Name, Value) :-
  374	\+ \+ time_property_name(Name),
  375	% When backtracking over time properties,
  376	% call set_time_file once and reuse the values for all properties,
  377	% so that the different times and timestamp/localtime are consistent with each other.
  378	set_time_file(FileOrDirectory, [changed(Ctime), modified(Mtime), access(Atime)], []),
  379	time_property(Name, Ctime, Mtime, Atime, Value).
  380
  381directory_property(Directory, Property) :- directory_property(Directory, Property, true).
  382
  383directory_property(Directory, readable, Value) :-
  384	directory_exists(Directory, read) -> Value = true ; Value = false.
  385directory_property(Directory, writable, Value) :-
  386	directory_exists(Directory, write) -> Value = true ; Value = false.
  387directory_property(Directory, searchable, Value) :-
  388	directory_exists(Directory, search) -> Value = true ; Value = false.
  389directory_property(Directory, Property, Value) :-
  390	file_or_directory_property(Directory, Property, Value).
  391
  392file_property(File, Property) :- file_property(File, Property, true).
  393
  394file_property(File, readable, Value) :-
  395	file_exists(File, read) -> Value = true ; Value = false.
  396file_property(File, writable, Value) :-
  397	file_exists(File, write) -> Value = true ; Value = false.
  398file_property(File, executable, Value) :-
  399	file_exists(File, execute) -> Value = true ; Value = false.
  400file_property(File, size_in_bytes, Value) :- size_file(File, Value).
  401file_property(File, Property, Value) :-
  402	file_or_directory_property(File, Property, Value).
  403
  404
  405%!	current_directory(-Directory) is det.
  406%!	current_directory(-Directory, +NewDirectory) is det.
  407%
  408%	Unifies Directory with the current working directory path.
  409%	In the 2-argument form, also changes the working directory to
  410%	the path NewDirectory.
  411
  412current_directory(Directory) :- working_directory(Directory, Directory).
  413current_directory(Directory, NewDirectory) :- working_directory(Directory, NewDirectory)