/******************************************************************************/ /* FILESYSTEM ABSTRACTION *****************************************************/ /******************************************************************************/ #include using namespace ebcl; #ifdef _WIN32 # define M_PATHSEP_ '\\' #else # define M_PATHSEP_ '/' # include #endif /*= T_FSPath =================================================================*/ bool T_FSPath::IsValidRoot( T_String const& str ) noexcept { if ( !str ) { return true; } #ifdef _WIN32 // TODO: support for network names (\\server\path) if ( str.length( ) == 3 && ( str.endsWith( ":\\" ) || str.endsWith( ":/" ) ) ) { return str[ 0 ].isAlpha( ); } else { return ( str == "/" || str == "\\" ); } #else return ( str == "/" || str == "\\" ); #endif } bool T_FSPath::IsValidComponent( T_String const& str ) noexcept { if ( !str ) { return false; } T_StringIterator it{ str }; while ( !it.atEnd( ) ) { const auto c{ it.character( ) }; it.next( ); if ( c.isControl( ) || c == '\\' || c == '/' || c == '"' || c == '<' || c == '>' || c == '|' || c == ':' || c == '*' || c == '?' ) { return false; } } return true; } /*----------------------------------------------------------------------------*/ T_FSPath::T_FSPath( T_String const& path ) noexcept { if ( !path ) { valid_ = true; return; } // Find sequences of (back)slashes in the string. For each sequence // we will store the positions of the first and last (back)slash. T_AutoArray< uint32_t , 64 > slashPos; T_StringIterator it{ path }; while ( !it.atEnd( ) ) { const auto c{ it.character( ) }; const auto p{ it.index( ) }; it.next( ); if ( c != '/' && c != '\\' ) { continue; } const auto ps{ slashPos.size( ) }; if ( ps == 0 || slashPos[ ps - 1 ] != p - 1 ) { slashPos.add( p ); slashPos.add( p ); } else { slashPos[ ps - 1 ] ++; } } // No (back)slashes, this is a relative path const auto sps{ slashPos.size( ) }; if ( sps == 0 ) { components_.add( path ); valid_ = IsValidComponent( path ); return; } // Collect the first item, with the first (back)slash included, // check if it's a root const auto firstSlashPos{ slashPos[ 0 ] }; T_String firstItem{ path.substr( 0 , 1 + firstSlashPos ) }; valid_ = true; if ( IsValidRoot( firstItem ) ) { root_ = std::move( firstItem ); } // Get and check the components uint32_t pos{ root_ ? 2u : 0u }; while ( pos <= sps ) { T_String item{ path.range( pos ? ( 1 + slashPos[ pos - 1 ] ) : 0 , pos == sps ? path.length( ) : ( slashPos[ pos ] - 1 ) ) }; assert( item || pos == sps ); if ( item ) { valid_ = valid_ && IsValidComponent( item ); components_.add( std::move( item ) ); } pos += 2; } } /*----------------------------------------------------------------------------*/ uint32_t T_FSPath::computeHash( ) const noexcept { uint32_t h{ ComputeHash( root_ ) }; const auto cs{ components_.size( ) }; h = ( ( h << 27 ) | ( h >> 5 ) ) ^ cs; for ( auto i = 0u ; i < cs ; i ++ ) { h = ( ( h << 27 ) | ( h >> 5 ) ) ^ ComputeHash( components_[ i ] ); } return h; } void T_FSPath::appendTo( T_StringBuilder& sb ) const noexcept { const auto cs{ components_.size( ) }; if ( !( root_ || cs ) ) { sb << '.'; return; } for ( auto i = 0u ; i < cs ; i ++ ) { if ( i == 0 ) { sb << root_; } else { sb << M_PATHSEP_; } sb << components_[ i ]; } } /*----------------------------------------------------------------------------*/ #ifdef _WIN32 # define M_CMPSTR_(A,B) (A).compareIgnoreCase( B ) #else # define M_CMPSTR_(A,B) (A).compare( B ) #endif int32_t T_FSPath::compareTo( T_FSPath const& other ) const noexcept { if ( &other == this ) { return 0; } int32_t cmp{ M_CMPSTR_( root_ , other.root_ ) }; if ( cmp == 0 ) { const auto nca{ components_.size( ) } , ncb{ other.components_.size( ) } , nc{ std::min( nca , ncb ) }; for ( auto i = 0u ; i < nc && cmp == 0 ; i ++ ) { cmp = M_CMPSTR_( components_[ i ] , other.components_[ i ] ); } if ( cmp == 0 ) { cmp = T_Comparator< uint32_t >::compare( nca , ncb ); } } return cmp; } /*----------------------------------------------------------------------------*/ T_FSPath T_FSPath::parent( ) const noexcept { T_FSPath p{ *this }; if ( p.components_.size( ) ) { p.components_.removeLast( ); } return p; } T_FSPath T_FSPath::child( T_String const& name ) const noexcept { T_FSPath c{ *this }; c.components_.add( name ); c.valid_ = c.valid_ && IsValidComponent( name ); return c; } T_FSPath T_FSPath::operator +( T_FSPath const& other ) const noexcept { T_FSPath p{ *this }; if ( other.root_ ) { p.valid_ = false; } else { p.valid_ = p.valid_ && other.valid_; } p.components_.addAll( other.components_ ); return p; } /*----------------------------------------------------------------------------*/ bool T_FSPath::inDirectoryOf( T_FSPath const& other ) const noexcept { if ( &other == this ) { return false; } if ( M_CMPSTR_( root_ , other.root_ ) != 0 ) { return false; } const auto nc{ components_.size( ) }; if ( nc != other.components_.size( ) || nc == 0 ) { return false; } for ( auto i = 0u ; i < nc - 1 ; i ++ ) { if ( M_CMPSTR_( components_[ i ] , other.components_[ i ] ) != 0 ) { return false; } } return M_CMPSTR_( components_[ nc - 1 ] , other.components_[ nc - 1 ] ) != 0; } bool T_FSPath::isParentOf( T_FSPath const& other ) const noexcept { if ( &other == this ) { return false; } if ( M_CMPSTR_( root_ , other.root_ ) != 0 ) { return false; } const auto nc{ components_.size( ) }; if ( nc + 1 != other.components_.size( ) ) { return false; } for ( auto i = 0u ; i < nc ; i ++ ) { if ( M_CMPSTR_( components_[ i ] , other.components_[ i ] ) != 0 ) { return false; } } return true; } bool T_FSPath::isAbove( T_FSPath const& other ) const noexcept { if ( &other == this ) { return false; } if ( M_CMPSTR_( root_ , other.root_ ) != 0 ) { return false; } const auto nc{ components_.size( ) }; if ( nc >= other.components_.size( ) ) { return false; } for ( auto i = 0u ; i < nc ; i ++ ) { if ( M_CMPSTR_( components_[ i ] , other.components_[ i ] ) != 0 ) { return false; } } return true; } /*----------------------------------------------------------------------------*/ T_FSPath T_FSPath::makeRelative( T_FSPath const& relTo ) const noexcept { assert( isValid( ) && relTo.isValid( ) ); assert( isCanonical( ) && relTo.isCanonical( ) ); if ( relTo.root_ != root_ ) { return *this; } const auto nca{ components_.size( ) } , ncb{ relTo.components_.size( ) } , nc{ std::min( nca , ncb ) }; uint32_t nCommon{ 0u }; for ( auto i = 0u ; i < nc ; i ++ ) { if ( M_CMPSTR_( components_[ i ] , relTo.components_[ i ] ) != 0 ) { break; } nCommon ++; } T_FSPath np; const T_String parent{ T_String::Pooled( ".." ) }; for ( auto i = nca ; i > nCommon ; i -- ) { np.components_.add( parent ); } for ( auto i = nCommon ; i < nca ; i ++ ) { np.components_.add( components_[ i ] ); } return np; } /*----------------------------------------------------------------------------*/ bool T_FSPath::isCanonical( ) const noexcept { if ( !( valid_ && root_ ) ) { return false; } auto const nc{ components_.size( ) }; for ( auto i = 0u ; i < nc ; i ++ ) { if ( components_[ i ] == "." || components_[ i ] == ".." ) { return false; } } return nc > 0; } T_FSPath T_FSPath::canonical( ) const noexcept { auto const nc{ components_.size( ) }; if ( !( valid_ && nc && root_ ) ) { return *this; } T_FSPath np; np.root_ = root_; for ( auto i = 0u ; i < nc ; i ++ ) { auto const& cmp{ components_[ i ] }; if ( cmp == ".." ) { if ( np.components_.size( ) ) { np.components_.removeLast( ); } } else if ( cmp != "." ) { np.components_.add( cmp ); } } return np; } /*= Filesystem ===============================================================*/ T_FSPath Filesystem::Cwd( ) noexcept { // TODO windows version T_Buffer< char > buffer{ 256 }; while ( getcwd( &buffer[ 0 ] , buffer.bytes( ) ) == nullptr ) { assert( errno == ERANGE ); buffer.resize( buffer.size( ) + 256 ); } T_FSPath path{ &buffer[ 0 ] }; assert( path.isValid( ) ); return path; }