View source with raw 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]).

SICStus 4 library(file_systems).

See also
- https://sicstus.sics.se/sicstus/docs/4.6.0/html/sicstus.html/lib_002dfile_005fsystems.html */
To be done
- This library is incomplete. As of SICStus 4.6.0, the following predicates are missing:
close_all_streams/0
Some predicates don't fully support all options available on SICStus. See the documentation for individual predicates for details.

The file access modes execute and search are interpreted slightly differently on SICStus and SWI. On SWI, execute and search are equivalent - both can be used with regular files and directories and will check execute or search permission depending on the file type, not the mode atom.

SICStus on the other hand checks the access modes only if the file in question has the appropriate type. Checking access mode execute on a directory or search on a regular file is equivalent to checking exist.

This difference affects not just file_exists/2 and directory_exists/2 in this library, but also the built-in absolute_file_name/3 with the option access(Mode).

On the other hand, file_property/2 and directory_property/2 with properties executable and searchable are not affected - here the emulation matches the native SICStus behavior.

  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.
 rename_file(+OldName, +NewName) is det
Like SWI's built-in rename_file/2, but only works on regular files. To rename directories, rename_directory/2 must be used.
  127rename_file(OldName, NewName) :-
  128	file_must_exist(OldName),
  129	system:rename_file(OldName, NewName).
 rename_directory(+OldName, +NewName) is det
Like SWI's built-in rename_file/2, but only works on directories. To rename regular files, rename_file/2 must be used.
  136rename_directory(OldName, NewName) :-
  137	directory_must_exist(OldName),
  138	system:rename_file(OldName, NewName).
 delete_file(+OldName) is det
Like SWI's built-in delete_file/1, but only works on regular files. To delete directories, delete_directory/1 must be used.
  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, _), _)).
 delete_directory(+OldName, +Options) is semidet
Extended verison of delete_directory/1. The only available option is if_nonempty(Value), which controls the behavior when OldName is not empty. Value may be ignore (silently succeed without deleting anything), fail (silently fail without deleting anything), error (throw an error - default behavior), and delete (recursively delete the directory and its contents, as if by delete_directory_and_contents/1 from library(filesex)).
  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).
 directory_exists(+Directory) is semidet
 directory_exists(+Directory, +Mode) is semidet
True if a directory exists at path Directory and can be accessed according to Mode (defaults to exist). Accepts the same access modes as absolute_file_name/3's access option.
  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).
 file_exists(+File) is semidet
 file_exists(+File, +Mode) is semidet
True if a regular file exists at path File and can be accessed according to Mode (defaults to exist). Accepts the same access modes as absolute_file_name/3's access option.
  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)]).
 file_must_exist(+File) is det
 file_must_exist(+File, +Mode) is det
Ensure that a regular file exists at path File and can be accessed according to Mode (defaults to exist). Otherwise an exception is thrown. Accepts the same access modes as absolute_file_name/3's access option.
  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)]).
 directory_must_exist(+Directory) is det
 directory_must_exist(+Directory, +Mode) is det
Ensure that a directory exists at path Directory and can be accessed according to Mode (defaults to exist). Otherwise an exception is thrown. Accepts the same access modes as absolute_file_name/3's access option.
  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).
 directory_member_of_directory(-BaseName, -FullName) is nondet
 directory_member_of_directory(+Directory, -BaseName, -FullName) is nondet
 directory_member_of_directory(+Directory, +Pattern, -BaseName, -FullName) is nondet
 file_member_of_directory(-BaseName, -FullName) is nondet
 file_member_of_directory(+Directory, -BaseName, -FullName) is nondet
 file_member_of_directory(+Directory, +Pattern, -BaseName, -FullName) is nondet
True if Directory contains a directory or regular file (respectively) named BaseName and the file's absolute path is FullName. If Directory is not given, it defaults to the current working directory. If Pattern is given, only succeeds if BaseName also matches that glob pattern.

These predicates enumerate all matching files on backtracking. This is also the intended usage pattern. For checking if a specific file/directory exists, or to get its absolute path, it's better to use file_exists/1, directory_exists/1, or absolute_file_name/3.

  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).
 directory_members_of_directory(-Set) is det
 directory_members_of_directory(+Directory, -Set) is det
 directory_members_of_directory(+Directory, +Pattern, -Set) is det
 file_members_of_directory(-Set) is det
 file_members_of_directory(+Directory, -Set) is det
 file_members_of_directory(+Directory, +Pattern, -Set) is det
Unifies Set with a set of BaseName-FullName entries for all directories or regular files (respectively) in Directory. If Directory is not given, it defaults to the current working directory. If Pattern is given, Set only includes entries where BaseName matches that glob pattern.
  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).
 file_property(+Path, ?Property) is semidet
 file_property(+Path, ?Property, -Value) is semidet
 directory_property(+Path, ?Property) is semidet
 directory_property(+Path, ?Property, -Value) is semidet
True if a regular file or directory (respectively) exists at Path and it has the given property and value. Property may be unbound to backtrack over all available properties. If the Value parameter is omitted, succeeds if Property has value true.

The following properties are currently supported:

create_timestamp
modify_timestamp
access_timestamp
The file/directory's creation/modification/access time as a Unix timestamp (as returned by SWI's set_time_file/3).
create_localtime
modify_localtime
access_localtime
The file/directory's creation/modification/access time as a datime/6 term (as returned by datime/2 from SICStus library(system)).
readable
writable
executable
searchable
true or false depending on whether the file/directory is readable/writable/executable/searchable. executable is only supported on regular files and searchable only on directories.
size_in_bytes
The file's size in bytes. Not supported on directories.

On Unix systems, create_timestamp/create_localtime don't return the file's actual creation time, but rather its "ctime" or "metadata change time". This matches the behavior of SICStus 4.6.0.

As of SICStus 4.6.0, the following properties are not yet emulated:

set_user_id
set_group_id
save_text
who_can_read
who_can_write
who_can_execute
who_can_search
owner_user_id
owner_group_id
owner_user_name
owner_group_name
  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).
 current_directory(-Directory) is det
 current_directory(-Directory, +NewDirectory) is det
Unifies Directory with the current working directory path. In the 2-argument form, also changes the working directory to the path NewDirectory.
  412current_directory(Directory) :- working_directory(Directory, Directory).
  413current_directory(Directory, NewDirectory) :- working_directory(Directory, NewDirectory)