inweb-bootstrap/foundation-module/Chapter_3/Filenames.w

292 lines
8.5 KiB
OpenEdge ABL
Raw Normal View History

2019-02-04 22:26:45 +00:00
[Filenames::] Filenames.
2020-04-15 22:45:08 +00:00
Names of hypothetical or real files in the filing system.
2019-02-04 22:26:45 +00:00
@h Storage.
Filename objects behave much like pathname ones, but they have their own
structure in order that the two are distinct C types. Individual filenames
are a single instance of the following. (Note that the text part is stored
as Unicode code points, regardless of what text encoding the locale has.)
=
typedef struct filename {
struct pathname *pathname_of_location;
struct text_stream *leafname;
2020-05-09 12:05:00 +00:00
CLASS_DEFINITION
2019-02-04 22:26:45 +00:00
} filename;
@h Creation.
A filename is made by supplying a pathname and a leafname.
=
2020-04-15 22:45:08 +00:00
filename *Filenames::in(pathname *P, text_stream *file_name) {
2019-02-04 22:26:45 +00:00
return Filenames::primitive(file_name, 0, Str::len(file_name), P);
}
filename *Filenames::primitive(text_stream *S, int from, int to, pathname *P) {
filename *F = CREATE(filename);
F->pathname_of_location = P;
if (to-from <= 0)
internal_error("empty intermediate pathname");
F->leafname = Str::new_with_capacity(to-from+1);
string_position pos = Str::at(S, from);
for (int i = from; i < to; i++, pos = Str::forward(pos))
PUT_TO(F->leafname, Str::get(pos));
return F;
}
@h Strings to filenames.
The following takes a textual name and returns a filename.
=
filename *Filenames::from_text(text_stream *path) {
int i = 0, pos = -1;
LOOP_THROUGH_TEXT(at, path) {
if (Platform::is_folder_separator(Str::get(at))) pos = i;
2019-02-04 22:26:45 +00:00
i++;
}
pathname *P = NULL;
if (pos >= 0) {
2020-06-27 22:03:14 +00:00
TEMPORARY_TEXT(PT)
Str::substr(PT, Str::at(path, 0), Str::at(path, pos));
P = Pathnames::from_text(PT);
2020-06-27 22:03:14 +00:00
DISCARD_TEXT(PT)
}
2019-02-04 22:26:45 +00:00
return Filenames::primitive(path, pos+1, Str::len(path), P);
}
filename *Filenames::from_text_relative(pathname *from, text_stream *path) {
filename *F = Filenames::from_text(path);
if (from) {
if (F->pathname_of_location == NULL) F->pathname_of_location = from;
else {
pathname *P = F->pathname_of_location;
while ((P) && (P->pathname_of_parent)) P = P->pathname_of_parent;
P->pathname_of_parent = from;
}
}
return F;
}
@h The writer.
And conversely:
=
void Filenames::writer(OUTPUT_STREAM, char *format_string, void *vF) {
filename *F = (filename *) vF;
if (F == NULL) WRITE("<no file>");
else {
if (F->pathname_of_location) {
Pathnames::writer(OUT, format_string, (void *) F->pathname_of_location);
if (format_string[0] == '/') PUT('/');
else PUT(FOLDER_SEPARATOR);
}
WRITE("%S", F->leafname);
}
}
@ And again relative to a given pathname:
=
void Filenames::to_text_relative(OUTPUT_STREAM, filename *F, pathname *P) {
2020-06-27 22:03:14 +00:00
TEMPORARY_TEXT(ft)
TEMPORARY_TEXT(pt)
2019-02-04 22:26:45 +00:00
WRITE_TO(ft, "%f", F);
WRITE_TO(pt, "%p", P);
int n = Str::len(pt);
if ((Str::prefix_eq(ft, pt, n)) && (Platform::is_folder_separator(Str::get_at(ft, n)))) {
2019-02-04 22:26:45 +00:00
Str::delete_n_characters(ft, n+1);
WRITE("%S", ft);
2021-08-11 11:01:50 +00:00
} else {
if (P == NULL) {
WRITE("%S", ft);
} else {
WRITE("..%c", FOLDER_SEPARATOR);
Filenames::to_text_relative(OUT, F, Pathnames::up(P));
}
}
2020-06-27 22:03:14 +00:00
DISCARD_TEXT(ft)
DISCARD_TEXT(pt)
2019-02-04 22:26:45 +00:00
}
2020-04-15 22:45:08 +00:00
@h Reading off the directory.
2019-02-04 22:26:45 +00:00
=
2020-04-15 22:45:08 +00:00
pathname *Filenames::up(filename *F) {
2019-02-04 22:26:45 +00:00
if (F == NULL) return NULL;
return F->pathname_of_location;
}
@h Reading off the leafname.
=
filename *Filenames::without_path(filename *F) {
2020-04-15 22:45:08 +00:00
return Filenames::in(NULL, F->leafname);
2019-02-04 22:26:45 +00:00
}
text_stream *Filenames::get_leafname(filename *F) {
if (F == NULL) return NULL;
return F->leafname;
}
void Filenames::write_unextended_leafname(OUTPUT_STREAM, filename *F) {
LOOP_THROUGH_TEXT(pos, F->leafname) {
wchar_t c = Str::get(pos);
if (c == '.') return;
PUT(c);
}
}
@h Filename extensions.
The following is cautiously written because of an oddity in Windows's handling
of filenames, which are allowed to have trailing dots or spaces, in a way
which isn't necessarily visible to the user, who may have added these by
an accidental brush of the keyboard. Thus |frog.jpg .| should be treated
as equivalent to |frog.jpg| when deciding the likely file format.
=
void Filenames::write_extension(OUTPUT_STREAM, filename *F) {
int on = FALSE;
LOOP_THROUGH_TEXT(pos, F->leafname) {
wchar_t c = Str::get(pos);
if (c == '.') on = TRUE;
if (on) PUT(c);
}
}
filename *Filenames::set_extension(filename *F, text_stream *extension) {
2020-06-27 22:03:14 +00:00
TEMPORARY_TEXT(NEWLEAF)
2019-02-04 22:26:45 +00:00
LOOP_THROUGH_TEXT(pos, F->leafname) {
wchar_t c = Str::get(pos);
if (c == '.') break;
PUT_TO(NEWLEAF, c);
}
if (Str::len(extension) > 0) {
if (Str::get_first_char(extension) != '.') WRITE_TO(NEWLEAF, ".");
WRITE_TO(NEWLEAF, "%S", extension);
2019-02-04 22:26:45 +00:00
}
2020-04-15 22:45:08 +00:00
filename *N = Filenames::in(F->pathname_of_location, NEWLEAF);
2020-06-27 22:03:14 +00:00
DISCARD_TEXT(NEWLEAF)
2019-02-04 22:26:45 +00:00
return N;
}
@h Guessing file formats.
The following guesses the file format from its file extension:
@d FORMAT_PERHAPS_HTML 1
@d FORMAT_PERHAPS_JPEG 2
@d FORMAT_PERHAPS_PNG 3
@d FORMAT_PERHAPS_OGG 4
@d FORMAT_PERHAPS_AIFF 5
@d FORMAT_PERHAPS_MIDI 6
@d FORMAT_PERHAPS_MOD 7
@d FORMAT_PERHAPS_GLULX 8
@d FORMAT_PERHAPS_ZCODE 9
@d FORMAT_PERHAPS_SVG 10
@d FORMAT_PERHAPS_GIF 11
@d FORMAT_UNRECOGNISED 0
=
int Filenames::guess_format(filename *F) {
2020-06-27 22:03:14 +00:00
TEMPORARY_TEXT(EXT)
2019-02-04 22:26:45 +00:00
Filenames::write_extension(EXT, F);
2020-06-27 22:03:14 +00:00
TEMPORARY_TEXT(NORMALISED)
2019-02-04 22:26:45 +00:00
LOOP_THROUGH_TEXT(pos, EXT) {
wchar_t c = Str::get(pos);
if (c != ' ') PUT_TO(NORMALISED, Characters::tolower(c));
}
2020-06-27 22:03:14 +00:00
DISCARD_TEXT(EXT)
2019-02-04 22:26:45 +00:00
int verdict = FORMAT_UNRECOGNISED;
if (Str::eq_wide_string(NORMALISED, L".html")) verdict = FORMAT_PERHAPS_HTML;
else if (Str::eq_wide_string(NORMALISED, L".htm")) verdict = FORMAT_PERHAPS_HTML;
else if (Str::eq_wide_string(NORMALISED, L".jpg")) verdict = FORMAT_PERHAPS_JPEG;
else if (Str::eq_wide_string(NORMALISED, L".jpeg")) verdict = FORMAT_PERHAPS_JPEG;
else if (Str::eq_wide_string(NORMALISED, L".png")) verdict = FORMAT_PERHAPS_PNG;
else if (Str::eq_wide_string(NORMALISED, L".ogg")) verdict = FORMAT_PERHAPS_OGG;
else if (Str::eq_wide_string(NORMALISED, L".aiff")) verdict = FORMAT_PERHAPS_AIFF;
else if (Str::eq_wide_string(NORMALISED, L".aif")) verdict = FORMAT_PERHAPS_AIFF;
else if (Str::eq_wide_string(NORMALISED, L".midi")) verdict = FORMAT_PERHAPS_MIDI;
else if (Str::eq_wide_string(NORMALISED, L".mid")) verdict = FORMAT_PERHAPS_MIDI;
else if (Str::eq_wide_string(NORMALISED, L".mod")) verdict = FORMAT_PERHAPS_MOD;
else if (Str::eq_wide_string(NORMALISED, L".svg")) verdict = FORMAT_PERHAPS_SVG;
else if (Str::eq_wide_string(NORMALISED, L".gif")) verdict = FORMAT_PERHAPS_GIF;
else if (Str::len(NORMALISED) > 0) {
if ((Str::get(Str::at(NORMALISED, 0)) == '.') &&
(Str::get(Str::at(NORMALISED, 1)) == 'z') &&
(Characters::isdigit(Str::get(Str::at(NORMALISED, 2)))) &&
(Str::len(NORMALISED) == 3))
verdict = FORMAT_PERHAPS_ZCODE;
else if (Str::get(Str::back(Str::end(NORMALISED))) == 'x')
verdict = FORMAT_PERHAPS_GLULX;
}
2020-06-27 22:03:14 +00:00
DISCARD_TEXT(NORMALISED)
2019-02-04 22:26:45 +00:00
return verdict;
}
@h Opening.
These files are wrappers for |fopen|, the traditional C library call, but
referring to the file by filename structure rather than a textual name. Note
that we must transcode the filename to whatever the locale expects before
we call |fopen|, which is the main reason for the wrapper.
=
FILE *Filenames::fopen(filename *F, char *usage) {
char transcoded_pathname[4*MAX_FILENAME_LENGTH];
2020-06-27 22:03:14 +00:00
TEMPORARY_TEXT(FN)
2019-02-04 22:26:45 +00:00
WRITE_TO(FN, "%f", F);
Str::copy_to_locale_string(transcoded_pathname, FN, 4*MAX_FILENAME_LENGTH);
2020-06-27 22:03:14 +00:00
DISCARD_TEXT(FN)
2019-02-04 22:26:45 +00:00
return fopen(transcoded_pathname, usage);
}
FILE *Filenames::fopen_caseless(filename *F, char *usage) {
char transcoded_pathname[4*MAX_FILENAME_LENGTH];
2020-06-27 22:03:14 +00:00
TEMPORARY_TEXT(FN)
2019-02-04 22:26:45 +00:00
WRITE_TO(FN, "%f", F);
Str::copy_to_locale_string(transcoded_pathname, FN, 4*MAX_FILENAME_LENGTH);
2020-06-27 22:03:14 +00:00
DISCARD_TEXT(FN)
2019-02-04 22:26:45 +00:00
return CIFilingSystem::fopen(transcoded_pathname, usage);
}
@h Comparing.
Not as easy as it seems. The following is a slow but effective way to
compare two filenames by seeing if they have the same canonical form
when printed out.
=
int Filenames::eq(filename *F1, filename *F2) {
if (F1 == F2) return TRUE;
2020-06-27 22:03:14 +00:00
TEMPORARY_TEXT(T1)
TEMPORARY_TEXT(T2)
2019-02-04 22:26:45 +00:00
WRITE_TO(T1, "%f", F1);
WRITE_TO(T2, "%f", F2);
int rv = Str::eq(T1, T2);
2020-06-27 22:03:14 +00:00
DISCARD_TEXT(T1)
DISCARD_TEXT(T2)
2019-02-04 22:26:45 +00:00
return rv;
}
2020-03-29 23:29:23 +00:00
2020-05-06 09:15:34 +00:00
@h Timestamps and sizes.
2020-03-29 23:29:23 +00:00
=
time_t Filenames::timestamp(filename *F) {
char transcoded_pathname[4*MAX_FILENAME_LENGTH];
2020-06-27 22:03:14 +00:00
TEMPORARY_TEXT(FN)
2020-03-29 23:29:23 +00:00
WRITE_TO(FN, "%f", F);
Str::copy_to_locale_string(transcoded_pathname, FN, 4*MAX_FILENAME_LENGTH);
time_t t = Platform::timestamp(transcoded_pathname);
2020-06-27 22:03:14 +00:00
DISCARD_TEXT(FN)
2020-03-29 23:29:23 +00:00
return t;
}
2020-05-06 09:15:34 +00:00
int Filenames::size(filename *F) {
char transcoded_pathname[4*MAX_FILENAME_LENGTH];
2020-06-27 22:03:14 +00:00
TEMPORARY_TEXT(FN)
2020-05-06 09:15:34 +00:00
WRITE_TO(FN, "%f", F);
Str::copy_to_locale_string(transcoded_pathname, FN, 4*MAX_FILENAME_LENGTH);
int t = (int) Platform::size(transcoded_pathname);
2020-06-27 22:03:14 +00:00
DISCARD_TEXT(FN)
2020-05-06 09:15:34 +00:00
return t;
}