BLI: factor out BLI_path_has_hidden_component, fix it and speed it up
Factors out utility function "does the path contain any hidden file/folder
components?" function out of innards of UI code (edit_file,
is_hidden_dot_filename) into a BLI function BLI_path_has_hidden_component
and then:
- Adds unit test coverage to it, which uncovered some inconsistencies
- Fix the behavior inconsistencies:
- A path component that is just a dot (.), was not considered hidden.
Unless it was the first folder component (now this is fixed).
- A path component that ended in a tilde (~) was considered hidden.
Unless it was the first folder component (now this is fixed).
- Speedup the function by not doing several recursive scans over the
string; instead all the logic is done in a single string scan.
Synthetic HasHiddenComponents_Performance test: 6.0s -> 1.1s for 50M calls
on Mac M1 Max.
More real world test (setup as in #120494): out of whole build_catalog_tree
time, the time taken by BLI_path_has_hidden_component drops from 37% down
to 28%
Pull Request: https://projects.blender.org/blender/blender/pulls/120541
This commit is contained in:
committed by
Aras Pranckevicius
parent
5b7bc88571
commit
9962c50b54
@@ -312,6 +312,17 @@ const char *BLI_path_basename(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Path Filtering Utilities
|
||||
* \{ */
|
||||
|
||||
/**
|
||||
* Return true if any path component starts with a dot.
|
||||
*/
|
||||
bool BLI_path_has_hidden_component(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Path Append
|
||||
* \{ */
|
||||
|
||||
@@ -2013,3 +2013,39 @@ int BLI_path_cmp_normalized(const char *p1, const char *p2)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BLI_path_has_hidden_component(const char *path)
|
||||
{
|
||||
bool component_start = true;
|
||||
char cur_char = path[0];
|
||||
char prev_char = '\0';
|
||||
|
||||
while (cur_char != '\0') {
|
||||
char next_char = path[1];
|
||||
/* If we're at a start of path component, current is '.'
|
||||
* and next one is not '.', end or separator: hidden. */
|
||||
if (component_start && cur_char == '.') {
|
||||
if (!ELEM(path[1], '.', '\0', '/', '\\')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
component_start = ELEM(cur_char, '/', '\\');
|
||||
/* Separator, and previous was tilde: hidden. */
|
||||
if (component_start && prev_char == '~') {
|
||||
return true;
|
||||
}
|
||||
|
||||
path++;
|
||||
prev_char = cur_char;
|
||||
cur_char = next_char;
|
||||
}
|
||||
|
||||
/* Was a tilde right at end of path: hidden. */
|
||||
if (prev_char == '~') {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Nothing was hidden. */
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
|
||||
#define DO_PERF_TESTS 0
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Local Utilities
|
||||
* \{ */
|
||||
@@ -1317,3 +1319,95 @@ TEST(path_util, Contains_Windows_case_insensitive)
|
||||
#endif /* WIN32 */
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Tests for: #BLI_path_has_hidden_component
|
||||
* \{ */
|
||||
|
||||
TEST(path_util, HasHiddenComponents)
|
||||
{
|
||||
/* No hidden components: */
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component(""));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component(" "));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component(".."));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("../.."));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("..\\.."));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a/../b"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a\\..\\b"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a~b"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a/b"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a\\b"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a/b/c"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a\\b\\c"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("/a/b"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("C:/a/b"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("C:/\a\\b"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a.txt"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a/b.txt"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a\\b.txt"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("/"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("\\"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a."));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a./b."));
|
||||
|
||||
/* Note: path component that is just a dot is not considered hidden. */
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("."));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a/."));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a\\."));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a/./b"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("a\\.\\b"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component("./a"));
|
||||
EXPECT_FALSE(BLI_path_has_hidden_component(".\\a"));
|
||||
|
||||
/* Does contain hidden components: */
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component(".a"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component(".a.b"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component("a/.b"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component("a\\.b"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component(".a/b"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component(".a\\b"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component(".a/.b"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component(".a\\.b"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component("a/.b.c"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component("a/.b/c.txt"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component("a\\.b\\c.txt"));
|
||||
|
||||
/* Tilde at end of path component: considered hidden. */
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component("~"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component("a~"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component("a/~/c"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component("a/b~/c"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component("a\\b~\\c"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component("~/b"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component("a~/b"));
|
||||
EXPECT_TRUE(BLI_path_has_hidden_component("a~\\b"));
|
||||
}
|
||||
|
||||
#if DO_PERF_TESTS
|
||||
|
||||
# include "BLI_timeit.hh"
|
||||
|
||||
TEST(path_util, HasHiddenComponents_Performance)
|
||||
{
|
||||
SCOPED_TIMER(__func__);
|
||||
const char *test_paths[] = {
|
||||
"test.txt",
|
||||
"test/a_fairly_long/path/here/shall_we/ok.txt",
|
||||
"test/a_fairly_long/path/here/.with_a_hidden_component/shall_we/ok.txt",
|
||||
"test/.another_path_with_hidden_component/yes.txt",
|
||||
"test/another/path/with/.hidden_filename",
|
||||
};
|
||||
const int RUN_COUNT = 10'000'000;
|
||||
int hidden = 0;
|
||||
for (int i = 0; i < RUN_COUNT; i++) {
|
||||
for (int j = 0; j < ARRAY_SIZE(test_paths); j++) {
|
||||
hidden += BLI_path_has_hidden_component(test_paths[j]) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(RUN_COUNT * 3, hidden);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -614,46 +614,6 @@ void filelist_setsorting(FileList *filelist, const short sort, bool invert_sort)
|
||||
|
||||
/* ********** Filter helpers ********** */
|
||||
|
||||
/* True if filename is meant to be hidden, eg. starting with period. */
|
||||
static bool is_hidden_dot_filename(const char *filename, const FileListInternEntry *file)
|
||||
{
|
||||
if (filename[0] == '.' && !ELEM(filename[1], '.', '\0')) {
|
||||
return true; /* ignore .file */
|
||||
}
|
||||
|
||||
int len = strlen(filename);
|
||||
if ((len > 0) && (filename[len - 1] == '~')) {
|
||||
return true; /* ignore file~ */
|
||||
}
|
||||
|
||||
/* filename might actually be a piece of path, in which case we have to check all its parts. */
|
||||
|
||||
bool hidden = false;
|
||||
char *sep = (char *)BLI_path_slash_rfind(filename);
|
||||
|
||||
if (!hidden && sep) {
|
||||
char tmp_filename[FILE_MAX_LIBEXTRA];
|
||||
|
||||
STRNCPY(tmp_filename, filename);
|
||||
sep = tmp_filename + (sep - filename);
|
||||
while (sep) {
|
||||
/* This happens when a path contains 'ALTSEP', '\' on Unix for e.g.
|
||||
* Supporting alternate slashes in paths is a bigger task involving changes
|
||||
* in many parts of the code, for now just prevent an assert, see #74579. */
|
||||
#if 0
|
||||
BLI_assert(sep[1] != '\0');
|
||||
#endif
|
||||
if (is_hidden_dot_filename(sep + 1, file)) {
|
||||
hidden = true;
|
||||
break;
|
||||
}
|
||||
*sep = '\0';
|
||||
sep = (char *)BLI_path_slash_rfind(tmp_filename);
|
||||
}
|
||||
}
|
||||
return hidden;
|
||||
}
|
||||
|
||||
/* True if should be hidden, based on current filtering. */
|
||||
static bool is_filtered_hidden(const char *filename,
|
||||
const FileListFilter *filter,
|
||||
@@ -675,7 +635,7 @@ static bool is_filtered_hidden(const char *filename,
|
||||
|
||||
#ifndef WIN32
|
||||
/* Check for unix-style names starting with period. */
|
||||
if ((filter->flags & FLF_HIDE_DOT) && is_hidden_dot_filename(filename, file)) {
|
||||
if ((filter->flags & FLF_HIDE_DOT) && BLI_path_has_hidden_component(filename)) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -3131,7 +3091,7 @@ static int filelist_readjob_list_dir(FileListReadJob *job_params,
|
||||
|
||||
#ifndef WIN32
|
||||
/* Set linux-style dot files hidden too. */
|
||||
if (is_hidden_dot_filename(entry->relpath, entry)) {
|
||||
if (BLI_path_has_hidden_component(entry->relpath)) {
|
||||
entry->attributes |= FILE_ATTR_HIDDEN;
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user