Переписан код для формирования путей к стандартным каталогам

This commit is contained in:
Andrei Astafev 2020-04-04 00:04:02 +03:00
parent 189d85719e
commit f87e6207d2
9 changed files with 244 additions and 216 deletions

View File

@ -20,6 +20,10 @@ option(BUILD_EXAMPLES "Build examples" OFF)
# Поиск библиотек с помощью pkgconfig
find_package(PkgConfig)
# Потоки
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
# Qt5
find_package(Qt5 COMPONENTS Core Network Gui Widgets DBus Concurrent Sql REQUIRED)

@ -1 +1 @@
Subproject commit 873e29592cf2c79986e358482fb5df67878c76b8
Subproject commit 47581bd7b7b8249dbd2854bf6bb4705d9612ba28

View File

@ -32,6 +32,8 @@ target_compile_options(${current_target} PUBLIC "${Qt5Core_EXECUTABLE_COMPILE_FL
target_link_libraries(${current_target} myx-filesystem)
target_link_libraries(${current_target} Qt5::Core)
target_link_libraries(${current_target} Threads::Threads)
# Имя выходного файла для цели
set_target_properties(${current_target}

View File

@ -1,3 +1,5 @@
#include "cmlib_private_config.hpp"
#include <myx/base/config.hpp>
#include <myx/filesystem/paths.hpp>
@ -6,17 +8,20 @@
namespace MF = myx::filesystem;
// Переменные для защиты экземпляра класса MF::Paths
std::atomic< MF::Paths* > MF::Paths::m_instance;
std::mutex MF::Paths::m_mutex;
int main( int argc, char** argv )
{
(void)argc;
(void)argv;
QCoreApplication::setApplicationName( PROJECT_NAME );
MF::Paths paths;
QCoreApplication::setApplicationName( CMLIB_PROJECT_NAME );
MF::Paths* paths = MF::Paths::getInstance();
paths.updatePaths();
paths.makeDefaultDirectories();
paths.findConfigFile( "test" );
paths->init( CMLIB_PROJECT_NAME, "conf" );
paths->makeDefaultDirectories();
paths->findConfigFile( "test" );
return( 0 );
}

View File

@ -25,7 +25,7 @@ add_dependencies(${current_target} base)
add_dependencies(${current_target} qt)
# Qt5
qt_translation(TARGET ${current_target} TS_DIR ${CMAKE_SOURCE_DIR}/l10n LANGUAGES ru_RU)
qt5_translation(TARGET ${current_target} TS_DIR ${CMAKE_SOURCE_DIR}/l10n LANGUAGES ru_RU)
target_include_directories(${current_target} PRIVATE ${CMAKE_SOURCE_DIR}/src)
target_include_directories(${current_target} SYSTEM PUBLIC ${Qt5Core_INCLUDE_DIRS})
target_compile_options(${current_target} PUBLIC "${Qt5Core_EXECUTABLE_COMPILE_FLAGS}")

View File

@ -9,34 +9,22 @@ namespace myx {
namespace filesystem {
#if !defined ( __linux__ )
error "Class CurrentExecutable is supported only in Linux"
#endif
CurrentExecutable::CurrentExecutable() :
m_procFilePath( QStringLiteral( "/proc/self/exe" ) )
{
auto canonicalFilePath = m_procFilePath.canonicalFilePath();
auto canonicalPath = m_procFilePath.canonicalPath();
m_fileName = canonicalFilePath.remove( canonicalPath ).remove( '/' );
m_canonicalFilePath = canonicalFilePath;
m_canonicalPath = canonicalPath;
m_canonicalFilePath = m_procFilePath.canonicalFilePath();
}
QFileInfo CurrentExecutable::canonicalFilePath() const
const QFileInfo& CurrentExecutable::canonicalFilePath() const
{
return( m_canonicalFilePath );
}
QDir CurrentExecutable::canonicalPath() const
{
return( m_canonicalPath );
}
QString CurrentExecutable::fileName() const
{
return( m_fileName );
}
} // namespace filesystem
} // namespace myx

View File

@ -19,34 +19,23 @@ namespace filesystem {
class CurrentExecutable
{
/// Путь к символической ссылке, указывающей на текущий исполняемый файл
/// @brief Путь к символической ссылке, указывающей на текущий исполняемый файл
QFileInfo m_procFilePath;
/// Канонический путь к текущему исполняемому файлу
/// @brief Канонический путь к текущему исполняемому файлу
QFileInfo m_canonicalFilePath;
/// Канонический путь к каталогу с текущим исполняемым файлом
QDir m_canonicalPath;
/// Имя текущего исполняемого файла
QString m_fileName;
friend class Paths;
public:
/**
* @brief Конструктор, собирающий информацию о текущем исполняемом файле.
* Иницализируются все внутренние переменные.
*/
CurrentExecutable();
/**
* @brief Канонический путь к текущему исполняемому файлу
*/
QFileInfo canonicalFilePath() const;
/**
* @brief Канонический путь к каталогу с текущим исполняемым файлом
*/
QDir canonicalPath() const;
/**
* @brief Имя текущего исполняемого файла
*/
QString fileName() const;
const QFileInfo& canonicalFilePath() const;
}; // class CurrentExecutable
} // namespace filesystem

View File

@ -10,77 +10,152 @@ namespace myx {
namespace filesystem {
Paths::Paths( QString configFileExtension ) :
m_prefixDirectory ( "/opt/" + QCoreApplication::organizationName().toLower() +
"/" + QCoreApplication::applicationName().toLower() ),
m_configFileExtension ( std::move( configFileExtension ) ),
m_projectDirectoryName( QCoreApplication::applicationName().toLower() )
Paths::Paths()
{
auto pd = m_prefixDirectory.absolutePath();
m_binaryDirectory = pd + "/bin";
// m_systemConfigDirectory = pd + "/etc/" + m_projectDirectoryName;
// m_userCacheDirectory = pd + "/var/lib/" + m_projectDirectoryName;
// m_logDirectory = pd + "/var/log/" + m_projectDirectoryName;
// m_dataDirectory = pd + "/share/" + m_projectDirectoryName;
// m_tempDirectory = QString::fromLocal8Bit( qgetenv( qPrintable( "TMPDIR" ) ) );
// m_homeDirectory = QString::fromLocal8Bit( qgetenv( qPrintable( "HOME" ) ) );
// m_configFileName = m_projectDirectoryName + "." + m_configFileExtension;
// m_configFilePath = m_systemConfigDirectory.absolutePath() + "/" + m_configFileName;
// if ( m_tempDirectory.absolutePath().isEmpty() || ( m_tempDirectory.path() == "." ) )
// {
// m_tempDirectory = QStringLiteral( _PATH_TMP );
// }
}
bool Paths::updatePaths()
Paths::HierarchyType Paths::getHierarchyType()
{
m_binaryDirectory = m_currentExecutable.canonicalPath();
QRegExp binRegexp( "/s*bin$" );
auto binaryDir = m_currentExecutable.m_canonicalFilePath.canonicalPath();
// if ( m_binaryDirectory.absolutePath().endsWith( "/bin" ) )
// {
// m_prefixDirectory = m_binaryDirectory.absolutePath().remove( QRegExp( "/bin$" ) );
// m_systemConfigDirectory = m_prefixDirectory.absolutePath() + "/etc/" + m_projectDirectoryName;
// m_userCacheDirectory = m_prefixDirectory.absolutePath() + "/var/lib/" + m_projectDirectoryName;
// m_logDirectory = m_prefixDirectory.absolutePath() + "/var/log/" + m_projectDirectoryName;
// m_dataDirectory = m_prefixDirectory.absolutePath() + "/share/" + m_projectDirectoryName;
// m_configFilePath = QFile( m_systemConfigDirectory.absolutePath() +
// "/" + QCoreApplication::applicationName() +
// "." + m_configFileExtension );
// }
if ( binRegexp.indexIn( binaryDir ) == -1 )
{
return ( HierarchyType::kFlat );
}
// if ( m_prefixDirectory.absolutePath().startsWith( "/opt" ) ||
// m_prefixDirectory.absolutePath().startsWith( "/usr" ) )
// {
// QString dataDirectory = QString::fromLocal8Bit( qgetenv( qPrintable( "XDG_DATA_HOME" ) ) );
// if ( dataDirectory.isEmpty() )
// {
// dataDirectory = m_homeDirectory.absolutePath() + ".local/share";
// }
// m_dataDirectory = dataDirectory + "/" +
// QCoreApplication::organizationName().toLower() + "/" +
// QCoreApplication::applicationName().toLower();
if ( binaryDir.startsWith( "/opt" ) )
{
QFileInfo etcDirInfo { "/opt/" + m_projectName + "/etc" };
if ( !etcDirInfo.isDir() || !etcDirInfo.isReadable() ) { return( HierarchyType::kFlat ); }
// QString configDirectory = QString::fromLocal8Bit( qgetenv( qPrintable( "XDG_CONFIG_HOME" ) ) );
// if ( configDirectory.isEmpty() )
// {
// configDirectory = m_homeDirectory.absolutePath() + ".config";
// }
// m_systemConfigDirectory = configDirectory + "/" +
// QCoreApplication::organizationName().toLower() + "/" +
// QCoreApplication::applicationName().toLower();
QFileInfo constDataDirInfo { "/opt/" + m_projectName + "/files/data" };
if ( !constDataDirInfo.isDir() || !constDataDirInfo.isReadable() ) { return( HierarchyType::kFlat ); }
// QString cacheDirectory = QString::fromLocal8Bit( qgetenv( qPrintable( "XDG_CACHE_HOME" ) ) );
// if ( cacheDirectory.isEmpty() )
// {
// cacheDirectory = m_homeDirectory.absolutePath() + ".cache";
// }
// m_userCacheDirectory = cacheDirectory + "/" +
// QCoreApplication::organizationName().toLower() + "/" +
// QCoreApplication::applicationName().toLower();
// m_logDirectory = m_userCacheDirectory.absolutePath() + "/log";
// }
QFileInfo varDataDirInfo { "/opt/" + m_projectName + "/files/lib" };
if ( !varDataDirInfo.isDir() || !varDataDirInfo.isWritable() ) { return( HierarchyType::kFlat ); }
QFileInfo logDirInfo { "/opt/" + m_projectName + "/files/log" };
if ( !logDirInfo.isDir() || !logDirInfo.isWritable() ) { return( HierarchyType::kFlat ); }
m_systemConfigDirectory = etcDirInfo.canonicalFilePath();
m_systemConstDataDirectory = constDataDirInfo.canonicalFilePath();
m_systemVarDataDirectory = varDataDirInfo.canonicalFilePath();
m_systemLogDirectory = logDirInfo.canonicalFilePath();
return ( HierarchyType::kOpt );
}
if ( binaryDir.startsWith( "/usr" ) )
{
QFileInfo etcDirInfo { "/etc/" + m_projectName };
if ( !etcDirInfo.isDir() || !etcDirInfo.isReadable() ) { return( HierarchyType::kFlat ); }
QFileInfo constDataDirInfo { "/usr/share/" + m_projectName };
if ( !constDataDirInfo.isDir() || !constDataDirInfo.isReadable() ) { return( HierarchyType::kFlat ); }
QFileInfo varDataDirInfo { "/var/lib/" + m_projectName };
if ( !varDataDirInfo.isDir() || !varDataDirInfo.isWritable() ) { return( HierarchyType::kFlat ); }
QFileInfo logDirInfo { "/var/log/" + m_projectName };
if ( !logDirInfo.isDir() || !logDirInfo.isWritable() ) { return( HierarchyType::kFlat ); }
m_systemConfigDirectory = etcDirInfo.canonicalFilePath();
m_systemConstDataDirectory = constDataDirInfo.canonicalFilePath();
m_systemVarDataDirectory = varDataDirInfo.canonicalFilePath();
m_systemLogDirectory = logDirInfo.canonicalFilePath();
return ( HierarchyType::kStandard );
}
if ( binaryDir.startsWith( m_homeDirectory.canonicalPath() + "/.local/bin" ) ||
binaryDir.startsWith( m_homeDirectory.canonicalPath() + "/bin" ) )
{
QFileInfo etcDirInfo { m_userConfigDirectory.canonicalPath() };
if ( !etcDirInfo.isDir() || !etcDirInfo.isReadable() ) { return( HierarchyType::kFlat ); }
QFileInfo constDataDirInfo { m_userConstDataDirectory.canonicalPath() };
if ( !constDataDirInfo.isDir() || !constDataDirInfo.isReadable() ) { return( HierarchyType::kFlat ); }
QFileInfo varDataDirInfo { m_userVarDataDirectory.canonicalPath() };
if ( !varDataDirInfo.isDir() || !varDataDirInfo.isWritable() ) { return( HierarchyType::kFlat ); }
QFileInfo logDirInfo { m_userLogDirectory.canonicalPath() };
if ( !logDirInfo.isDir() || !logDirInfo.isWritable() ) { return( HierarchyType::kFlat ); }
m_systemConfigDirectory = etcDirInfo.canonicalFilePath();
m_systemConstDataDirectory = constDataDirInfo.canonicalFilePath();
m_systemVarDataDirectory = varDataDirInfo.canonicalFilePath();
m_systemLogDirectory = logDirInfo.canonicalFilePath();
return( HierarchyType::kHome );
}
binaryDir.remove( binRegexp );
QFileInfo etcDirInfo { binaryDir + "/etc" };
if ( !etcDirInfo.isDir() || !etcDirInfo.isReadable() ) { return( HierarchyType::kFlat ); }
QFileInfo constDataDirInfo { binaryDir + "/files/data" };
if ( !constDataDirInfo.isDir() || !constDataDirInfo.isReadable() ) { return( HierarchyType::kFlat ); }
QFileInfo varDataDirInfo { binaryDir + "/files/lib" };
if ( !varDataDirInfo.isDir() || !varDataDirInfo.isWritable() ) { return( HierarchyType::kFlat ); }
QFileInfo logDirInfo { binaryDir + "/files/log" };
if ( !logDirInfo.isDir() || !logDirInfo.isWritable() ) { return( HierarchyType::kFlat ); }
m_systemConfigDirectory = etcDirInfo.canonicalFilePath();
m_systemConstDataDirectory = constDataDirInfo.canonicalFilePath();
m_systemVarDataDirectory = varDataDirInfo.canonicalFilePath();
m_systemLogDirectory = logDirInfo.canonicalFilePath();
return ( HierarchyType::kUser );
} // Paths::getHierarchyType
bool Paths::init( const QString& projectDir, const QString& configFileExtension )
{
m_projectName = projectDir.isEmpty() ? m_currentExecutable.m_canonicalFilePath.fileName()
: projectDir;
m_configFileExtension = configFileExtension.isEmpty() ? "conf"
: configFileExtension;
m_configFileName = m_projectName + "." + m_configFileExtension;
m_homeDirectory = QString::fromLocal8Bit( qgetenv( qPrintable( "HOME" ) ) );
m_tempDirectory = QString::fromLocal8Bit( qgetenv( qPrintable( "TMPDIR" ) ) );
if ( m_tempDirectory.canonicalPath().isEmpty() || ( m_tempDirectory.path() == "." ) )
{
m_tempDirectory = QStringLiteral( _PATH_TMP );
}
auto configHome { QString::fromLocal8Bit( qgetenv( qPrintable( "XDG_CONFIG_HOME" ) ) ) };
if ( configHome.isEmpty() )
{
configHome = m_homeDirectory.canonicalPath() + "/.config";
}
m_userConfigDirectory = configHome + "/" + m_projectName;
auto dataHome { QString::fromLocal8Bit( qgetenv( qPrintable( "XDG_DATA_HOME" ) ) ) };
if ( dataHome.isEmpty() )
{
dataHome = m_homeDirectory.canonicalPath() + "/.local/share";
}
dataHome += "/" + m_projectName;
m_userConstDataDirectory = dataHome + "/data";
m_userVarDataDirectory = dataHome + "/lib";
m_userLogDirectory = dataHome + "/log";
m_hierarchyType = getHierarchyType();
if ( m_hierarchyType == HierarchyType::kFlat )
{
m_systemConstDataDirectory = m_currentExecutable.m_canonicalFilePath.canonicalPath();
m_systemVarDataDirectory = m_currentExecutable.m_canonicalFilePath.canonicalPath();
m_systemConfigDirectory = m_currentExecutable.m_canonicalFilePath.canonicalPath();
m_systemLogDirectory = m_currentExecutable.m_canonicalFilePath.canonicalPath();
}
return( true );
} // Paths::updatePaths
@ -100,7 +175,7 @@ bool Paths::makeDefaultDirectories()
QString Paths::findConfigFile( const QString& defaultConfigFile )
{
if ( QFileInfo( defaultConfigFile ).isReadable() )
if ( !defaultConfigFile.isEmpty() && QFileInfo( defaultConfigFile ).isReadable() )
{
m_configFilePath = defaultConfigFile;
return( defaultConfigFile );
@ -123,27 +198,9 @@ QString Paths::findConfigFile( const QString& defaultConfigFile )
} // Paths::findConfigFile
const QDir& Paths::prefixDirectory() const
QDir Paths::binaryDirectory() const
{
return( m_prefixDirectory );
}
void Paths::setPrefixDirectory( const QString& prefixDirectory )
{
m_prefixDirectory = prefixDirectory;
}
const QDir& Paths::binaryDirectory() const
{
return( m_binaryDirectory );
}
void Paths::setBinaryDirectory( const QString& binaryDirectory )
{
m_binaryDirectory = binaryDirectory;
return( m_currentExecutable.m_canonicalFilePath.dir() );
}
@ -171,18 +228,6 @@ void Paths::setSystemConfigDirectory( const QString& systemConfigDirectory )
}
const QDir& Paths::localConfigDirectory() const
{
return( m_localConfigDirectory );
}
void Paths::setLocalConfigDirectory( const QString& localConfigDirectory )
{
m_localConfigDirectory = localConfigDirectory;
}
const QFileInfo& Paths::configFilePath() const
{
return( m_configFilePath );
@ -243,18 +288,6 @@ void Paths::setSystemVarDataDirectory( const QString& systemVarDataDirectory )
}
const QDir& Paths::localVarDataDirectory() const
{
return( m_localVarDataDirectory );
}
void Paths::setLocalVarDataDirectory( const QString& localVarDataDirectory )
{
m_localVarDataDirectory = localVarDataDirectory;
}
const QDir& Paths::userConstDataDirectory() const
{
return( m_userConstDataDirectory );
@ -279,18 +312,6 @@ void Paths::setSystemConstDataDirectory( const QString& systemConstDataDirectory
}
const QDir& Paths::localConstDataDirectory() const
{
return( m_localConstDataDirectory );
}
void Paths::setLocalConstDataDirectory( const QString& localConstDataDirectory )
{
m_localConstDataDirectory = localConstDataDirectory;
}
const QDir& Paths::userLogDirectory() const
{
return( m_userLogDirectory );
@ -315,18 +336,6 @@ void Paths::setSystemLogDirectory( const QString& systemLogDirectory )
}
const QDir& Paths::localLogDirectory() const
{
return( m_localLogDirectory );
}
void Paths::setLocalLogDirectory( const QString& localLogDirectory )
{
m_localLogDirectory = localLogDirectory;
}
const QDir& Paths::tempDirectory() const
{
return( m_tempDirectory );
@ -347,19 +356,19 @@ const QDir& Paths::homeDirectory() const
const QString& Paths::projectDirectoryName() const
{
return( m_projectDirectoryName );
return( m_projectName );
}
void Paths::setProjectDirectoryName( const QString& projectDirectoryName )
{
m_projectDirectoryName = projectDirectoryName;
m_projectName = projectDirectoryName;
}
const QString& Paths::executableFileName() const
QString Paths::executableFileName() const
{
return( m_currentExecutable.m_fileName );
return( m_currentExecutable.m_canonicalFilePath.fileName() );
}

View File

@ -14,6 +14,10 @@
#include <QDir>
#include <QFileInfo>
#include <atomic>
#include <future>
#include <mutex>
#include <thread>
namespace myx {
@ -21,17 +25,50 @@ namespace filesystem {
class Paths
{
/// @brief Путь к базовому каталогу
QDir m_prefixDirectory;
/// @brief Путь к каталогу с исполняемым файлом
QDir m_binaryDirectory;
enum class HierarchyType : intptr_t
{
kSplit = 0x00,
kFlat = 0x01,
kOpt = 0x02,
kStandard = 0x04,
kUser = 0x08,
kHome = 0x10,
};
/// @brief Тип расположения файлов по каталогам
HierarchyType m_hierarchyType { HierarchyType::kFlat };
/// @brief Параметры текущего исполняемого файла
CurrentExecutable m_currentExecutable;
/// @brief Имя проекта, которое используется при формировании имён файлов и каталогов
QString m_projectName;
/// @brief Путь к каталогу с временными файлами
QDir m_tempDirectory;
/// @brief Путь к домашнему каталогу текущего пользователя
QDir m_homeDirectory;
/// @brief Путь к пользовательскому каталогу с изменяемыми файлами
QDir m_userVarDataDirectory;
/// @brief Путь к системному каталогу с изменяемыми файлами
QDir m_systemVarDataDirectory;
/// @brief Путь к пользовательскому каталогу с неизменяемыми файлами
QDir m_userConstDataDirectory;
/// @brief Путь к системному каталогу с неизменяемыми файлами
QDir m_systemConstDataDirectory;
/// @brief Путь к пользовательскому каталогу с журналами работы
QDir m_userLogDirectory;
/// @brief Путь к системному каталогу с журналами работы
QDir m_systemLogDirectory;
/// @brief Путь к пользовательскому каталогу с файлами настройки
QDir m_userConfigDirectory;
/// @brief Путь к системному каталогу с файлами настройки
QDir m_systemConfigDirectory;
/// @brief Путь к локальному каталогу с файлами настройки
QDir m_localConfigDirectory;
/// @brief Полный путь к файлу настройки
QFileInfo m_configFilePath;
/// @brief Имя файла настройки
@ -39,48 +76,42 @@ class Paths
/// @brief Расширение для файла настройки
QString m_configFileExtension;
/// @brief Путь к пользовательскому каталогу с изменяемыми файлами
QDir m_userVarDataDirectory;
/// @brief Путь к системному каталогу с изменяемыми файлами
QDir m_systemVarDataDirectory;
/// @brief Путь к локальному каталогу с изменяемыми файлами
QDir m_localVarDataDirectory;
Paths();
~Paths() = default;
Paths( const Paths& ) = delete;
Paths& operator=( const Paths& ) = delete;
/// @brief Путь к пользовательскому каталогу с неизменяемыми файлами
QDir m_userConstDataDirectory;
/// @brief Путь к системному каталогу с неизменяемыми файлами
QDir m_systemConstDataDirectory;
/// @brief Путь к локальному каталогу с неизменяемыми файлами
QDir m_localConstDataDirectory;
HierarchyType getHierarchyType();
/// @brief Путь к пользовательскому каталогу с журналами работы
QDir m_userLogDirectory;
/// @brief Путь к системному каталогу с журналами работы
QDir m_systemLogDirectory;
/// @brief Путь к локальному каталогу с журналами работы
QDir m_localLogDirectory;
/// @brief Путь к каталогу с временными файлами
QDir m_tempDirectory;
/// @brief Путь к домашнему каталогу текущего пользователя
QDir m_homeDirectory;
/// @brief Имя подкаталога для проекта
QString m_projectDirectoryName;
/// Параметры текущего исполняемого файла
CurrentExecutable m_currentExecutable;
static std::atomic< Paths* > m_instance;
static std::mutex m_mutex;
public:
/**
* @brief Конструктор
* @param configFileExtension Расширение для файла настройки
* @brief getInstance
* @return Уникальный экземпляр класса Paths
*/
Paths( QString configFileExtension = "conf" );
static Paths* getInstance()
{
Paths* localInstance = m_instance.load( std::memory_order_acquire );
if ( !localInstance )
{
std::lock_guard< std::mutex > myLock( m_mutex );
localInstance = m_instance.load( std::memory_order_relaxed );
if ( !localInstance )
{
localInstance = new Paths();
m_instance.store( localInstance, std::memory_order_release );
}
}
return( localInstance );
}
/**
* @brief Обновление путей с учётом расположения исполняемого файла
*/
bool updatePaths();
bool init( const QString& projectDir, const QString& configFileExtension = "conf" );
/**
* @brief Создание стандартных каталогов
@ -109,7 +140,7 @@ public:
/**
* @brief Получение пути к каталогу с исполняемым файлом
*/
const QDir& binaryDirectory() const;
QDir binaryDirectory() const;
/**
* @brief Установка пути к каталогу с исполняемым файлом
*/
@ -276,7 +307,7 @@ public:
/**
* @brief Имя исполняемого файла
*/
const QString& executableFileName() const;
QString executableFileName() const;
/**
* @brief Полный путь к исполняемому файлу
*/