Skip to content

Commit

Permalink
DisplayNamesCache's API now uses std::string_view. NativeHost::FetchS…
Browse files Browse the repository at this point in the history
…ingleItemListing uses routines from PathManip.
  • Loading branch information
mikekazakov committed Dec 19, 2024
1 parent e7e260f commit 12ac971
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 68 deletions.
18 changes: 9 additions & 9 deletions Source/VFS/source/Native/DisplayNamesCache.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2014-2020 Michael Kazakov. Subject to GNU General Public License version 3.
// Copyright (C) 2014-2024 Michael Kazakov. Subject to GNU General Public License version 3.
#pragma once

#include <sys/stat.h>
Expand All @@ -17,18 +17,18 @@ class DisplayNamesCache
public:
static DisplayNamesCache &Instance();

// nullptr string means that there's no dispay string for this
const char *DisplayName(const std::string &_path);
const char *DisplayName(const struct stat &_st, const std::string &_path);
const char *DisplayName(ino_t _ino, dev_t _dev, const std::string &_path);
// nullopt string means that there's no dispay string for this
std::optional<std::string_view> DisplayName(std::string_view _path);
std::optional<std::string_view> DisplayName(const struct stat &_st, std::string_view _path);
std::optional<std::string_view> DisplayName(ino_t _ino, dev_t _dev, std::string_view _path);

private:
std::optional<const char *> Fast_Unlocked(ino_t _ino, dev_t _dev, const std::string &_path) const noexcept;
void Commit_Locked(ino_t _ino, dev_t _dev, const std::string &_path, const char *_dispay_name);
std::optional<std::string_view> Fast_Unlocked(ino_t _ino, dev_t _dev, std::string_view _path) const noexcept;
void Commit_Locked(ino_t _ino, dev_t _dev, std::string_view _path, std::string_view _dispay_name);

struct Filename {
const char *fs_filename;
const char *display_filename;
std::string_view fs_filename;
std::string_view display_filename; // empty string means that there's no display name for this item
};
using Inodes = std::unordered_multimap<ino_t, Filename>;
using Devices = std::unordered_map<dev_t, Inodes>;
Expand Down
101 changes: 63 additions & 38 deletions Source/VFS/source/Native/DisplayNamesCache.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2,87 +2,101 @@
#include "DisplayNamesCache.h"
#include <Foundation/Foundation.h>
#include <Utility/StringExtras.h>
#include <Utility/PathManip.h>
#include <ankerl/unordered_dense.h>
#include <Base/UnorderedUtil.h>

namespace nc::vfs::native {

DisplayNamesCache &DisplayNamesCache::Instance()
static std::string_view Internalize(std::string_view _string) noexcept
{
static auto inst = new DisplayNamesCache; // never free
return *inst;
[[clang::no_destroy]] static constinit std::mutex mtx;
[[clang::no_destroy]] static ankerl::unordered_dense::
segmented_set<std::string, UnorderedStringHashEqual, UnorderedStringHashEqual> strings;

std::lock_guard lock{mtx};
if( auto it = strings.find(_string); it != strings.end() ) {
return *it;
}
else {
return *strings.emplace(_string).first;
}
}

static bool is_same_filename(const char *_filename, const std::string &_path)
DisplayNamesCache &DisplayNamesCache::Instance()
{
const auto p = _path.rfind('/');
return p != std::string::npos && strcmp(_filename, _path.c_str() + p + 1) == 0;
[[clang::no_destroy]] static DisplayNamesCache inst;
return inst;
}

std::optional<const char *>
DisplayNamesCache::Fast_Unlocked(ino_t _ino, dev_t _dev, const std::string &_path) const noexcept
std::optional<std::string_view>
DisplayNamesCache::Fast_Unlocked(ino_t _ino, dev_t _dev, std::string_view _path) const noexcept
{
const auto inodes = m_Devices.find(_dev);
if( inodes == end(m_Devices) )
if( inodes == m_Devices.end() )
return std::nullopt;

const std::string_view filename = utility::PathManip::Filename(_path);

const auto range = inodes->second.equal_range(_ino);
for( auto i = range.first; i != range.second; ++i )
if( is_same_filename(i->second.fs_filename, _path) )
if( i->second.fs_filename == filename )
return i->second.display_filename;

return std::nullopt;
}

static const char *filename_dup(const std::string &_path)
{
auto i = _path.rfind('/');
if( i == std::string::npos )
return "";
return strdup(_path.c_str() + i + 1);
}

void DisplayNamesCache::Commit_Locked(ino_t _ino, dev_t _dev, const std::string &_path, const char *_dispay_name)
void DisplayNamesCache::Commit_Locked(ino_t _ino, dev_t _dev, std::string_view _path, std::string_view _dispay_name)
{
Filename f;
f.fs_filename = filename_dup(_path);
f.fs_filename = Internalize(utility::PathManip::Filename(_path));
f.display_filename = _dispay_name;
const std::lock_guard<spinlock> guard(m_WriteLock);
m_Devices[_dev].insert(std::make_pair(_ino, f));
}

const char *DisplayNamesCache::DisplayName(const struct stat &_st, const std::string &_path)
std::optional<std::string_view> DisplayNamesCache::DisplayName(const struct stat &_st, std::string_view _path)
{
return DisplayName(_st.st_ino, _st.st_dev, _path);
}

const char *DisplayNamesCache::DisplayName(const std::string &_path)
std::optional<std::string_view> DisplayNamesCache::DisplayName(std::string_view _path)
{
std::array<char, 512> mem_buffer;
std::pmr::monotonic_buffer_resource mem_resource(mem_buffer.data(), mem_buffer.size());
const std::pmr::string path(_path, &mem_resource);

struct stat st;
if( stat(_path.c_str(), &st) != 0 )
return nullptr;
if( stat(path.c_str(), &st) != 0 )
return std::nullopt;
return DisplayName(st, _path);
}

static NSFileManager *filemanager = NSFileManager.defaultManager;
static const char *Slow(const std::string &_path)
static std::string_view Slow(std::string_view _path)
{
NSString *const path = [NSString stringWithUTF8StdStringNoCopy:_path];
NSString *const path = [NSString stringWithUTF8StdStringView:_path];
if( path == nil )
return nullptr; // can't create string for this path.
return {}; // can't create string for this path.

NSString *display_name = [filemanager displayNameAtPath:path];
if( display_name == nil )
return nullptr; // something strange has happen
return {}; // something strange has happen

display_name = [display_name decomposedStringWithCanonicalMapping];
const char *display_utf8_name = display_name.UTF8String;
assert(display_name.UTF8String != nullptr);
const std::string_view display_utf8_name = display_name.UTF8String;

if( strcmp(_path.c_str() + _path.rfind('/') + 1, display_utf8_name) == 0 )
return nullptr; // this display name is exactly like the filesystem one
if( display_utf8_name.empty() )
return {}; // ignore empty display names

return strdup(display_utf8_name);
if( utility::PathManip::Filename(_path) == display_utf8_name )
return {}; // this display name is exactly like the filesystem one

return Internalize(display_utf8_name);
}

const char *DisplayNamesCache::DisplayName(ino_t _ino, dev_t _dev, const std::string &_path)
std::optional<std::string_view> DisplayNamesCache::DisplayName(ino_t _ino, dev_t _dev, std::string_view _path)
{
// many readers, one writer | readers preference, based on atomic spinlocks

Expand All @@ -99,14 +113,25 @@ static bool is_same_filename(const char *_filename, const std::string &_path)
m_WriteLock.unlock();
m_ReadLock.unlock();

if( existed )
return *existed;
if( existed ) {
if( existed->empty() ) {
return std::nullopt;
}
else {
return *existed;
}
}
// FAST PATH ENDS

// SLOW PATH BEGINS
const auto generated_str = Slow(_path);
Commit_Locked(_ino, _dev, _path, generated_str);
return generated_str;
const std::string_view internalized_str = Slow(_path);
Commit_Locked(_ino, _dev, _path, internalized_str);
if( internalized_str.empty() ) {
return std::nullopt;
}
else {
return internalized_str;
}
// SLOW PATH ENDS
}

Expand Down
33 changes: 18 additions & 15 deletions Source/VFS/source/Native/Host.mm
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@
static auto &dnc = DisplayNamesCache::Instance();
if( auto display_name = dnc.DisplayName(
_params.inode, _params.dev, listing_source.directories[0] + listing_source.filenames[_n]) )
listing_source.display_filenames.insert(_n, display_name);
listing_source.display_filenames.insert(_n, std::string(*display_name));
}

ext_flags[_n] = _params.ext_flags;
Expand Down Expand Up @@ -258,18 +258,21 @@
if( _cancel_checker && _cancel_checker() )
return VFSError::Cancelled;

// TODO: rewrite without using C-style strings
char path[MAXPATHLEN];
char directory[MAXPATHLEN];
char filename[MAXPATHLEN];
memcpy(path, _path.data(), _path.length());
path[_path.length()] = 0;
std::array<char, 512> mem_buffer;
std::pmr::monotonic_buffer_resource mem_resource(mem_buffer.data(), mem_buffer.size());
const std::pmr::string path(utility::PathManip::WithoutTrailingSlashes(_path), &mem_resource);
if( path.empty() )
return VFSError::InvalidCall;

if( !EliminateTrailingSlashInPath(path) || !GetDirectoryContainingItemFromPath(path, directory) ||
!GetFilenameFromPath(path, filename) )
const std::string_view directory = utility::PathManip::Parent(path);
if( directory.empty() )
return VFSError::InvalidCall;

auto &io = routedio::RoutedIO::InterfaceForAccess(path, R_OK);
const std::string_view filename = utility::PathManip::Filename(path);
if( filename.empty() )
return VFSError::InvalidCall;

auto &io = routedio::RoutedIO::InterfaceForAccess(path.c_str(), R_OK);

using nc::base::variable_container;
uint64_t ext_flags = 0;
Expand Down Expand Up @@ -312,8 +315,8 @@
if( S_ISDIR(listing_source.unix_modes[0]) && !listing_source.filenames[0].empty() &&
listing_source.filenames[0] != ".." ) {
static auto &dnc = DisplayNamesCache::Instance();
if( auto display_name = dnc.DisplayName(_params.inode, _params.dev, path) )
listing_source.display_filenames.insert(0, display_name);
if( std::optional<std::string_view> display_name = dnc.DisplayName(_params.inode, _params.dev, path) )
listing_source.display_filenames.insert(0, std::string(*display_name));
}

ext_flags = _params.ext_flags;
Expand All @@ -327,15 +330,15 @@
if( listing_source.unix_types[0] == DT_LNK ) {
// read an actual link path
char linkpath[MAXPATHLEN];
const ssize_t sz = io.readlink(path, linkpath, MAXPATHLEN);
const ssize_t sz = io.readlink(path.c_str(), linkpath, MAXPATHLEN);
if( sz != -1 ) {
linkpath[sz] = 0;
listing_source.symlinks.insert(0, linkpath);
}

// stat the target file
struct stat stat_buffer;
const auto stat_ret = io.stat(path, &stat_buffer);
const auto stat_ret = io.stat(path.c_str(), &stat_buffer);
if( stat_ret == 0 ) {
listing_source.unix_modes[0] = stat_buffer.st_mode;
listing_source.unix_flags[0] = MergeUnixFlags(listing_source.unix_flags[0], stat_buffer.st_flags);
Expand All @@ -349,7 +352,7 @@
// syscalls).
if( (_flags & Flags::F_LoadTags) && !(ext_flags & EF_NO_XATTRS) ) {
// TODO: is it worth routing the I/O here? guess not atm
const int entry_fd = open(path, O_RDONLY | O_NONBLOCK);
const int entry_fd = open(path.c_str(), O_RDONLY | O_NONBLOCK);
if( entry_fd >= 0 ) {
auto close_entry_fd = at_scope_end([entry_fd] { close(entry_fd); });
if( auto tags = utility::Tags::ReadTags(entry_fd); !tags.empty() )
Expand Down
4 changes: 2 additions & 2 deletions Source/VFS/source/Native/SpecialDirectories.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ int FetchUnifiedListing(NativeHost &_host,
if( (_flags & VFSFlags::F_LoadDisplayNames) != 0 ) {
auto &cache = DisplayNamesCache::Instance();
if( auto userpath_name = cache.DisplayName(_user_path) )
input.title = userpath_name;
input.title = *userpath_name;
else if( auto systempath_name = cache.DisplayName(_system_path) )
input.title = systempath_name;
input.title = *systempath_name;
else if( auto nonloc_name = MakeNonLocalizedTitle(_user_path) )
input.title = nonloc_name;
}
Expand Down
4 changes: 0 additions & 4 deletions Source/VFS/tests/Host_UT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@
using namespace nc;
using namespace nc::vfs;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Return;

TEST_CASE(PREFIX "FetchSingleItemListing")
{
Expand Down

0 comments on commit 12ac971

Please sign in to comment.