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) {
|
2020-07-06 22:01:05 +00:00
|
|
|
if (Platform::is_folder_separator(Str::get(at))) pos = i;
|
2019-02-04 22:26:45 +00:00
|
|
|
i++;
|
|
|
|
}
|
|
|
|
pathname *P = NULL;
|
2019-02-08 10:54:05 +00:00
|
|
|
if (pos >= 0) {
|
2020-06-27 22:03:14 +00:00
|
|
|
TEMPORARY_TEXT(PT)
|
2019-02-08 10:54:05 +00:00
|
|
|
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-08 10:54:05 +00:00
|
|
|
}
|
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);
|
2020-07-06 22:01:05 +00:00
|
|
|
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);
|
|
|
|
} else internal_error("filename not relative to pathname");
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-26 10:48:21 +00:00
|
|
|
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);
|
|
|
|
}
|
2020-04-26 10:48:21 +00:00
|
|
|
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;
|
|
|
|
}
|