Names of hypothetical or real files in the filing system.
- §1. Storage
- §2. Creation
- §3. Strings to filenames
- §4. The writer
- §6. Reading off the directory
- §7. Reading off the leafname
- §8. Filename extensions
- §10. Guessing file formats
- §11. Opening
- §12. Comparing
- §13. Timestamps and sizes
- §14. Renaming
- §15. Copying
- §16. Moving
§1. 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.)
classdef filename { struct pathname *pathname_of_location; struct text_stream *leafname; }
- The structure filename is private to this section.
filename *Filenames::in(pathname *P, text_stream *file_name) { 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; }
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; i++; } pathname *P = NULL; if (pos >= 0) { TEMPORARY_TEXT(PT) Str::substr(PT, Str::at(path, 0), Str::at(path, pos)); P = Pathnames::from_text(PT); DISCARD_TEXT(PT) } 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; }
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); } }
void Filenames::to_text_relative(OUTPUT_STREAM, filename *F, pathname *P) { TEMPORARY_TEXT(ft) TEMPORARY_TEXT(pt) 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)))) { Str::delete_n_characters(ft, n+1); WRITE("%S", ft); } else { if (P == NULL) { WRITE("%S", ft); } else { WRITE("..%c", FOLDER_SEPARATOR); Filenames::to_text_relative(OUT, F, Pathnames::up(P)); } } DISCARD_TEXT(ft) DISCARD_TEXT(pt) }
§6. Reading off the directory.
pathname *Filenames::up(filename *F) { if (F == NULL) return NULL; return F->pathname_of_location; }
filename *Filenames::without_path(filename *F) { return Filenames::in(NULL, F->leafname); } text_stream *Filenames::get_leafname(filename *F) { if (F == NULL) return NULL; return F->leafname; }
§8. Filename extensions. The following functions are written cautiously 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_unextended_leafname(OUTPUT_STREAM, filename *F) { LOOP_THROUGH_TEXT(pos, F->leafname) { inchar32_t c = Str::get(pos); if (c == '.') return; PUT(c); } } void Filenames::write_extension(OUTPUT_STREAM, filename *F) { TEMPORARY_TEXT(EXT) int on = FALSE; LOOP_THROUGH_TEXT(pos, F->leafname) { inchar32_t c = Str::get(pos); if (c == '.') on = TRUE; if (on) PUT_TO(EXT, c); } while ((Characters::is_Unicode_whitespace(Str::get_last_char(EXT))) || (Str::get_last_char(EXT) == '.')) Str::delete_last_character(EXT); WRITE("%S", EXT); DISCARD_TEXT(EXT) } filename *Filenames::set_extension(filename *F, text_stream *extension) { TEMPORARY_TEXT(NEWLEAF) Filenames::write_unextended_leafname(NEWLEAF, F); if (Str::len(extension) > 0) { if (Str::get_first_char(extension) != '.') WRITE_TO(NEWLEAF, "."); WRITE_TO(NEWLEAF, "%S", extension); } filename *N = Filenames::in(F->pathname_of_location, NEWLEAF); DISCARD_TEXT(NEWLEAF) return N; }
void Filenames::write_final_extension(OUTPUT_STREAM, filename *F) { TEMPORARY_TEXT(EXT) Filenames::write_extension(EXT, F); int penultimate_dot = -1, last_dot = -1; for (int i=0; i<Str::len(EXT); i++) { inchar32_t c = Str::get_at(EXT, i); if (c == '.') { penultimate_dot = last_dot; last_dot = i; } } if (last_dot >= 0) for (int i=last_dot; i<Str::len(EXT); i++) PUT(Str::get_at(EXT, i)); DISCARD_TEXT(EXT) } void Filenames::write_penultimate_extension(OUTPUT_STREAM, filename *F) { TEMPORARY_TEXT(EXT) Filenames::write_extension(EXT, F); int penultimate_dot = -1, last_dot = -1; for (int i=0; i<Str::len(EXT); i++) { inchar32_t c = Str::get_at(EXT, i); if (c == '.') { penultimate_dot = last_dot; last_dot = i; } } if (penultimate_dot >= 0) for (int i=penultimate_dot; i<last_dot; i++) PUT(Str::get_at(EXT, i)); DISCARD_TEXT(EXT) }
define FORMAT_PERHAPS_HTML 1
define FORMAT_PERHAPS_JPEG 2
define FORMAT_PERHAPS_PNG 3
define FORMAT_PERHAPS_OGG 4
define FORMAT_PERHAPS_AIFF 5
define FORMAT_PERHAPS_MIDI 6
define FORMAT_PERHAPS_MOD 7
define FORMAT_PERHAPS_GLULX 8
define FORMAT_PERHAPS_ZCODE 9
define FORMAT_PERHAPS_SVG 10
define FORMAT_PERHAPS_GIF 11
define FORMAT_UNRECOGNISED 0
int Filenames::guess_format(filename *F) { TEMPORARY_TEXT(EXT) Filenames::write_extension(EXT, F); TEMPORARY_TEXT(NORMALISED) LOOP_THROUGH_TEXT(pos, EXT) { inchar32_t c = Str::get(pos); if (c != ' ') PUT_TO(NORMALISED, Characters::tolower(c)); } DISCARD_TEXT(EXT) int verdict = FORMAT_UNRECOGNISED; if (Str::eq_wide_string(NORMALISED, U".html")) verdict = FORMAT_PERHAPS_HTML; else if (Str::eq_wide_string(NORMALISED, U".htm")) verdict = FORMAT_PERHAPS_HTML; else if (Str::eq_wide_string(NORMALISED, U".jpg")) verdict = FORMAT_PERHAPS_JPEG; else if (Str::eq_wide_string(NORMALISED, U".jpeg")) verdict = FORMAT_PERHAPS_JPEG; else if (Str::eq_wide_string(NORMALISED, U".png")) verdict = FORMAT_PERHAPS_PNG; else if (Str::eq_wide_string(NORMALISED, U".ogg")) verdict = FORMAT_PERHAPS_OGG; else if (Str::eq_wide_string(NORMALISED, U".aiff")) verdict = FORMAT_PERHAPS_AIFF; else if (Str::eq_wide_string(NORMALISED, U".aif")) verdict = FORMAT_PERHAPS_AIFF; else if (Str::eq_wide_string(NORMALISED, U".midi")) verdict = FORMAT_PERHAPS_MIDI; else if (Str::eq_wide_string(NORMALISED, U".mid")) verdict = FORMAT_PERHAPS_MIDI; else if (Str::eq_wide_string(NORMALISED, U".mod")) verdict = FORMAT_PERHAPS_MOD; else if (Str::eq_wide_string(NORMALISED, U".svg")) verdict = FORMAT_PERHAPS_SVG; else if (Str::eq_wide_string(NORMALISED, U".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; } DISCARD_TEXT(NORMALISED) return verdict; }
§11. 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]; TEMPORARY_TEXT(FN) WRITE_TO(FN, "%f", F); Str::copy_to_locale_string(transcoded_pathname, FN, 4*MAX_FILENAME_LENGTH); DISCARD_TEXT(FN) return fopen(transcoded_pathname, usage); } FILE *Filenames::fopen_caseless(filename *F, char *usage) { char transcoded_pathname[4*MAX_FILENAME_LENGTH]; TEMPORARY_TEXT(FN) WRITE_TO(FN, "%f", F); Str::copy_to_locale_string(transcoded_pathname, FN, 4*MAX_FILENAME_LENGTH); DISCARD_TEXT(FN) return CIFilingSystem::fopen(transcoded_pathname, usage); }
§12. 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; TEMPORARY_TEXT(T1) TEMPORARY_TEXT(T2) WRITE_TO(T1, "%f", F1); WRITE_TO(T2, "%f", F2); int rv = Str::eq(T1, T2); DISCARD_TEXT(T1) DISCARD_TEXT(T2) return rv; } int Filenames::eq_insensitive(filename *F1, filename *F2) { if (F1 == F2) return TRUE; TEMPORARY_TEXT(T1) TEMPORARY_TEXT(T2) WRITE_TO(T1, "%f", F1); WRITE_TO(T2, "%f", F2); int rv = Str::eq_insensitive(T1, T2); DISCARD_TEXT(T1) DISCARD_TEXT(T2) return rv; }
time_t Filenames::timestamp(filename *F) { char transcoded_pathname[4*MAX_FILENAME_LENGTH]; TEMPORARY_TEXT(FN) WRITE_TO(FN, "%f", F); Str::copy_to_locale_string(transcoded_pathname, FN, 4*MAX_FILENAME_LENGTH); time_t t = Platform::timestamp(transcoded_pathname); DISCARD_TEXT(FN) return t; } int Filenames::size(filename *F) { char transcoded_pathname[4*MAX_FILENAME_LENGTH]; TEMPORARY_TEXT(FN) WRITE_TO(FN, "%f", F); Str::copy_to_locale_string(transcoded_pathname, FN, 4*MAX_FILENAME_LENGTH); int t = (int) Platform::size(transcoded_pathname); DISCARD_TEXT(FN) return t; }
§14. Renaming. If this succeeds, the filename F is altered to match the new name, and
the function returns TRUE; if not, F is unchanged, and FALSE.
int Filenames::rename(filename *F, text_stream *new_name) { text_stream *old_name = Filenames::get_leafname(F); if (Str::eq(old_name, new_name)) return TRUE; filename *G = Filenames::in(Filenames::up(F), new_name); TEMPORARY_TEXT(old_path) TEMPORARY_TEXT(new_path) WRITE_TO(old_path, "%f", F); WRITE_TO(new_path, "%f", G); char old_name_written_out[4*MAX_FILENAME_LENGTH]; Str::copy_to_locale_string(old_name_written_out, old_path, 4*MAX_FILENAME_LENGTH); char new_name_written_out[4*MAX_FILENAME_LENGTH]; Str::copy_to_locale_string(new_name_written_out, new_path, 4*MAX_FILENAME_LENGTH); int rv = Platform::rename_file(old_name_written_out, new_name_written_out); if (rv) { Str::clear(F->leafname); Str::copy(F->leafname, new_name); } DISCARD_TEXT(old_path) DISCARD_TEXT(new_path) return rv; }
void Filenames::copy_file(filename *from, filename *to) { TEMPORARY_TEXT(from_path) TEMPORARY_TEXT(to_path) WRITE_TO(from_path, "%f", from); WRITE_TO(to_path, "%f", to); char from_name_written_out[4*MAX_FILENAME_LENGTH]; Str::copy_to_locale_string(from_name_written_out, from_path, 4*MAX_FILENAME_LENGTH); char to_name_written_out[4*MAX_FILENAME_LENGTH]; Str::copy_to_locale_string(to_name_written_out, to_path, 4*MAX_FILENAME_LENGTH); Platform::copy_file(from_name_written_out, to_name_written_out); DISCARD_TEXT(from_path) DISCARD_TEXT(to_path) }
int Filenames::move_file(filename *from, filename *to) { TEMPORARY_TEXT(from_path) TEMPORARY_TEXT(to_path) WRITE_TO(from_path, "%f", from); WRITE_TO(to_path, "%f", to); char from_name_written_out[4*MAX_FILENAME_LENGTH]; Str::copy_to_locale_string(from_name_written_out, from_path, 4*MAX_FILENAME_LENGTH); char to_name_written_out[4*MAX_FILENAME_LENGTH]; Str::copy_to_locale_string(to_name_written_out, to_path, 4*MAX_FILENAME_LENGTH); int rv = Platform::rename_file(from_name_written_out, to_name_written_out); DISCARD_TEXT(from_path) DISCARD_TEXT(to_path) return rv; }