// Options for each library folder (a top-level folder of the library, one per place imported from), // kept in .library-folders in the library itself, so they go where the library goes: // // @version 1 // // [folder "Photos 2014"] // segment: true // // segment: segmentation scans (app/segment_ui.jai) read the images in it. A folder not listed, or an // option not given, is off. Keys this build does not know are kept as they are when the file is // written again, so a later build's options survive an earlier one. Main thread only. LIBRARY_FOLDERS_NAME :: ".library-folders"; Library_Folder :: struct { name: string; // as viewer captures name it: "SnagitLibraryWork"; "" for the library's top segment: bool; other_lines: [..] string; // key: value lines this build does not know, as they were } library_folders: struct { folders: [..] Library_Folder; // heap; in the file's order, new ones last loaded: bool; } library_folders_path :: () -> string { return tprint("%/%", viewer_folder(), LIBRARY_FOLDERS_NAME); } // Reads .library-folders; a missing file is every option off. Mistakes are logged and skipped. library_folders_load :: () { path := library_folders_path(); text, ok := read_entire_file(path, log_errors = false,, temp); library_folders_parse(ifx ok then text else "", path); } // Writes .library-folders beside itself and swaps it in. library_folders_save :: () { path := library_folders_path(); temp_path := tprint("%.tmp", path); if !write_entire_file(temp_path, library_folders_text()) || !atomic_file_replace_or_just_rename_if_destination_file_does_not_exist(temp_path, path) { log_error("[Library] Could not write %.", path); } } // The options in text, a .library-folders file's (path is for messages), in place of those held. library_folders_parse :: (text: string, path: string) { push_allocator(context.default_allocator); library_folders_clear(); library_folders.folders.allocator = context.default_allocator; library_folders.loaded = true; folder: *Library_Folder; version_seen := false; for raw, line_index: split(text, "\n",, temp) { line := trim(trim_right(raw, "\r")); if !line.count || line[0] == #char "#" continue; if begins_with(line, "@version") { version, version_ok := string_to_int(trim(advance(line, 8))); if !version_ok || version != 1 { log_error("[Library] % is version %, which this build does not read; its options are off.", path, trim(advance(line, 8))); library_folders_clear(); return; } version_seen = true; continue; } if !version_seen { log_error("[Library] % does not start with @version 1; its options are off.", path); return; } if begins_with(line, "[folder ") && ends_with(line, "]") { name, rest, name_ok := mcap_read_quoted(trim(slice(line, 8, line.count - 9))); if !name_ok || trim(rest).count { log_error("[Library] %:%: expected [folder \"NAME\"].", path, line_index + 1); folder = null; continue; } folder = library_folder_find_or_add(name); continue; } colon := find_index_from_left(line, #char ":"); if !folder || colon < 0 { log_error("[Library] %:%: expected key: value under a [folder \"NAME\"]. Ignored.", path, line_index + 1); continue; } key := trim(slice(line, 0, colon)); value := trim(advance(line, colon + 1)); if key == "segment" && (value == "true" || value == "false") folder.segment = value == "true"; else array_add(*folder.other_lines, copy_string(line)); } } // The options held, as a .library-folders file. Folders with no option on and nothing kept are left // out. Temp. library_folders_text :: () -> string { builder: String_Builder; builder.allocator = temp; append(*builder, "@version 1\n"); append(*builder, "# Options for each library folder, set in mcap's Segmentation window. See src/app/library_folders.jai.\n"); for library_folders.folders { if !it.segment && !it.other_lines.count continue; print_to_builder(*builder, "\n[folder %]\n", mcap_quote(it.name)); print_to_builder(*builder, "segment: %\n", ifx it.segment then "true" else "false"); for line: it.other_lines print_to_builder(*builder, "%\n", line); } return builder_to_string(*builder,, temp); } library_folder_segments :: (name: string) -> bool { for library_folders.folders if it.name == name return it.segment; return false; } library_folder_set_segment :: (name: string, on: bool) { folder := library_folder_find_or_add(name); if folder.segment == on return; folder.segment = on; library_folders_save(); log("[Library] Segmentation % for %.", ifx on then "on" else "off", ifx name then name else "the library's top"); } // Folders the file has options for that the library no longer holds (renamed or deleted). Temp. library_folders_gone :: () -> [..] string { gone: [..] string; gone.allocator = temp; for library_folders.folders if (it.segment || it.other_lines.count) && !array_find(viewer.folders, it.name) array_add(*gone, it.name); return gone; } library_folder_forget :: (name: string) { for library_folders.folders { if it.name != name continue; free(it.name,, context.default_allocator); for line: it.other_lines free(line,, context.default_allocator); array_free(it.other_lines); array_ordered_remove_by_index(*library_folders.folders, it_index); break; } library_folders_save(); } #scope_file library_folder_find_or_add :: (name: string) -> *Library_Folder { for * library_folders.folders if it.name == name return it; folder := array_add(*library_folders.folders); folder.* = .{name = copy_string(name,, context.default_allocator)}; folder.other_lines.allocator = context.default_allocator; return folder; } library_folders_clear :: () { for library_folders.folders { free(it.name,, context.default_allocator); for line: it.other_lines free(line,, context.default_allocator); array_free(it.other_lines); } array_reset(*library_folders.folders); }