Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ jobs:
xmake repo -u

- name: 配置项目
# 依赖包安装要从网络取源码并编译,卡住时必须尽快失败,别耗满 6 小时的默认超时
timeout-minutes: 60
run: |
xmake f -cy --with_test=y --check_warnings=n -m ${{ matrix.mode }} -a ${{ matrix.target.arch }}
xmake show
Expand Down
78 changes: 6 additions & 72 deletions src/AstCore/Data/SpiceAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,84 +189,18 @@ SpiceAPI::~SpiceAPI()
{
kclear(); // 清除所有已加载的内核
unload(); // 卸载库
releaseShortPathAliases();
}

void SpiceAPI::makeSpicePath(StringView filepath, std::string &spicePath)
{
spicePath.clear();
if(filepath.empty())
return;

fs::path path(filepath);
aUTF8ToCRT(path.c_str(), spicePath);
std::string temp(filepath);
aUTF8ToCRT(temp.c_str(), spicePath);
if(spicePath.size() <= kSpiceFilenameMaxLength)
return;

// CSPICE 不接受超长路径,只能借道短路径别名:
// 把超长的父目录映射到临时目录下的一个短路径,再把文件名接上去
fs::path dir = path.parent_path();
fs::path name = path.filename();

auto it = shortPathAliases_.find(dir.string());
if(it == shortPathAliases_.end())
{
// 别名根目录:临时目录下按进程 + 本对象地址命名,
// 保证同一进程内的多个 SpiceAPI 实例互不干扰
if(shortPathRoot_.empty())
{
std::error_code ec;
fs::path tempDir = fs::temp_directory_path(ec);
if(tempDir.empty())
tempDir = "./tmp/";
shortPathRoot_ = (tempDir / (
"ast-spice-" + std::to_string(posix::getpid()) + "-"
+ std::to_string(reinterpret_cast<uintptr_t>(this))
)).string();
fs::remove_all(shortPathRoot_); // 清理上次异常退出留下的同名目录
fs::create_directories(shortPathRoot_, ec);
if(ec)
{
shortPathRoot_.clear();
aWarning(_("无法创建短路径别名目录,超长路径 '%s' 将直接交给 CSPICE"), dir.c_str());
return;
}
}

fs::path aliasDir = (fs::path(shortPathRoot_) / std::to_string(shortPathAliases_.size()));
std::error_code ec;

// 创建符号链接时需要使用绝对路径
fs::path absDir = fs::absolute(dir, ec);
if(ec)
return;

// Windows 用目录联接,创建不需要管理员权限;其它平台用符号链接
#ifdef _WIN32
errc_t rc = aCreateJunction(absDir.string(), aliasDir.string());
#else
errc_t rc = aCreateSymbolicLink(absDir.string(), aliasDir.string(), true);
#endif
if(rc != eNoError)
{
aWarning(_("为超长路径 '%s' 创建短路径别名失败,将直接交给 CSPICE"), dir.c_str());
return;
}
it = shortPathAliases_.emplace(dir, aliasDir).first;
}

fs::path aliasPath = fs::path(it->second) / name;
aUTF8ToCRT(aliasPath.c_str(), spicePath);
}

void SpiceAPI::releaseShortPathAliases()
{
if(shortPathRoot_.empty())
return;
// 联接点/符号链接只会删除别名本身,不会删到目标目录里的内核文件
fs::remove_all(shortPathRoot_);
shortPathRoot_.clear();
shortPathAliases_.clear();
std::string shortPath = aTryGetShortPath(filepath);
aUTF8ToCRT(shortPath.c_str(), spicePath);
if(spicePath.size() > kSpiceFilenameMaxLength)
aWarning(_("路径超过 CSPICE 的长度限制且无法映射到短路径,CSPICE 将拒绝该文件 '%s'"), temp.c_str());
}

errc_t SpiceAPI::load(StringView dirpath)
Expand Down
11 changes: 3 additions & 8 deletions src/AstCore/Data/SpiceAPI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,26 +176,21 @@ class AST_CORE_API SpiceAPI
void kclear();
protected:
errc_t checkerror();

/// @brief 将路径转换为 CSPICE 可接受的路径
/// @details CSPICE 的 FILSIZ 是 256:FURNSH/SPKLEF 在打开文件前就会拒绝
/// 超过 255 字符的文件名,与操作系统的长路径支持无关。超长路径
/// 的父目录会被映射到一个短路径别名目录下再拼接文件名。
/// @param path 原始路径
/// @param spicePath 供 CSPICE 使用的路径
/// @param spicePath 供 CSPICE 使用的路径(C库运行时编码)
void makeSpicePath(StringView path, std::string& spicePath);

/// @brief 删除创建过的短路径别名目录
void releaseShortPathAliases();


A_DISABLE_COPY(SpiceAPI);
protected:
void* library_{nullptr}; ///< 库句柄
std::vector<uint32_t> spk_handles_{}; ///< 已加载的SPK内核句柄列表
funcarray functions_{}; ///< 函数指针
std::mutex mutex_{}; ///< 互斥锁(CSPICE库的函数不是线程安全的,这里用于保护函数调用的线程安全问题)
std::map<std::string, std::string> shortPathAliases_{}; ///< 超长父目录 -> 短路径别名目录
std::string shortPathRoot_{}; ///< 短路径别名根目录
};


Expand Down
125 changes: 125 additions & 0 deletions src/AstUtil/Platform/Path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@

#include "Path.hpp"
#include "AstUtil/Encode.hpp"
#include "AstUtil/Posix.hpp"
#include "AstUtil/FileSystem.hpp"
#include "AstUtil/Logger.hpp"
#include <climits>
#include <cstddef>
#include <cstring>
#include <vector>
#include <map>
#include <mutex>
#ifdef _WIN32
#include <Windows.h>
#include <winioctl.h> // FSCTL_SET_REPARSE_POINT
Expand Down Expand Up @@ -329,5 +334,125 @@ errc_t aCreateSymbolicLink(StringView targetPath, StringView linkPath, bool isDi
#endif
}

// --------------------------------------
// 目录映射器:用于将超长路径映射为短路径别名
// --------------------------------------


namespace{

/// @brief 目录路径映射器
class DirectoryMapper
{
public:
static DirectoryMapper& Instance();
DirectoryMapper() = default;
~DirectoryMapper() { releaseShortPathAliases(); } ///< 进程退出时清理别名目录

std::string getShortPath(StringView path);
protected:
void releaseShortPathAliases();
public:
std::mutex mutex_{}; ///< 本类被多个模块共用,需要线程安全
std::map<std::string, std::string> shortPathAliases_{}; ///< 超长父目录 -> 短路径别名目录
std::string shortPathRoot_{}; ///< 短路径别名根目录
};

DirectoryMapper& DirectoryMapper::Instance()
{
static DirectoryMapper instance;
return instance;
}

std::string DirectoryMapper::getShortPath(StringView dirpath)
{
if(dirpath.empty())
return std::string();

fs::path path(dirpath);

std::lock_guard<std::mutex> lock(mutex_);
auto it = shortPathAliases_.find(path.string());
if(it == shortPathAliases_.end())
{
// 别名根目录:临时目录下按进程 + 本对象地址命名
if(shortPathRoot_.empty())
{
std::error_code ec;
fs::path tempDir = fs::temp_directory_path(ec);
if(tempDir.empty())
{
aWarning(_("无法创建短路径别名目录,直接返回超长路径 '%s'"), path.c_str());
return path.string();
}
shortPathRoot_ = (tempDir / (
"ast-dirmapper-" + std::to_string(posix::getpid()) + "-"
+ std::to_string(reinterpret_cast<uintptr_t>(this))
)).string();
fs::remove_all(shortPathRoot_); // 清理上次异常退出留下的同名目录
fs::create_directories(shortPathRoot_, ec);
if(ec)
{
shortPathRoot_.clear();
aWarning(_("无法创建短路径别名目录,直接返回超长路径 '%s'"), path.c_str());
return path.string();
}
}

fs::path aliasDir = (fs::path(shortPathRoot_) / std::to_string(shortPathAliases_.size()));
std::error_code ec;

// 创建符号链接时需要使用绝对路径
fs::path absDir = fs::absolute(path, ec);
if(ec)
return path.string();

// Windows 用目录联接,创建不需要管理员权限;其它平台用符号链接
#ifdef _WIN32
errc_t rc = aCreateJunction(absDir.string(), aliasDir.string());
#else
errc_t rc = aCreateSymbolicLink(absDir.string(), aliasDir.string(), true);
#endif
if(rc != eNoError)
{
aWarning(_("为超长路径 '%s' 创建短路径别名失败,直接返回超长路径 '%s'"), path.c_str(), path.c_str());
return path.string();
}
it = shortPathAliases_.emplace(path, aliasDir).first;
}

return it->second;
}

void DirectoryMapper::releaseShortPathAliases()
{
std::lock_guard<std::mutex> lock(mutex_);
if(shortPathRoot_.empty())
return;
// 联接点/符号链接只会删除别名本身,不会删除目标目录里的文件
fs::remove_all(shortPathRoot_);
shortPathRoot_.clear();
shortPathAliases_.clear();
}

}

std::string aTryGetShortDirPath(StringView dirpath)
{
return DirectoryMapper::Instance().getShortPath(dirpath);
}


std::string aTryGetShortPath(StringView path)
{
fs::path p(path);
if(fs::is_directory(p))
return aTryGetShortDirPath(path);
fs::path parent = p.parent_path();
fs::path name = p.filename();
fs::path newParent = aTryGetShortDirPath(parent.string());
fs::path newPath = (newParent / name);
return newPath.string();
}

AST_NAMESPACE_END
7 changes: 7 additions & 0 deletions src/AstUtil/Platform/Path.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ AST_UTIL_CAPI errc_t aCreateJunction(StringView targetPath, StringView junctionP
/// @return 错误码
AST_UTIL_CAPI errc_t aCreateSymbolicLink(StringView targetPath, StringView linkPath, bool isDirectory);


/// @brief 尝试获取短路径
/// @param path 原始路径
/// @return 短路径
AST_UTIL_API std::string aTryGetShortPath(StringView path);


/*! @} */

AST_NAMESPACE_END
19 changes: 19 additions & 0 deletions src/AstWeather/Magnetosphere/igrf/igrf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
#include "AstUtil/Literals.hpp"
#include "AstUtil/Math.hpp"

#ifdef _WIN32
#include <Windows.h> // for MAX_PATH
#endif

#ifdef AST_WITH_LIBF2C
#include "f2c.h"

Expand Down Expand Up @@ -77,6 +81,21 @@ void igrf(double lon, double lat, double height, double year, double &xl, double
#ifdef AST_WITH_LIBF2C
fs::path datadir = aDataDir();
datadir /= AST_DEFAULT_DIR_IGRF;
#ifdef _WIN32
/*
参考 SetCurrentDirectory 函数文档:
链接:https://learn.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-setcurrentdirectory

windows 下工作目录路径长度不能超过 MAX_PATH(只有从 Windows 10 版本 1607 开始才能通过显式启用来支持)

null 字符之前的最后一个字符必须是反斜杠 ('\') 。如果未指定反斜杠,则会添加反斜杠
因此,需要为路径指定 >MAX_PATH-2 个字符,除非包含尾随反斜杠
*/
std::error_code ec;
fs::path absolute_path = fs::absolute(datadir, ec);
if(absolute_path.string().size() > MAX_PATH - 2)
datadir = aTryGetShortPath(absolute_path.string());
#endif
// 切换工作目录到 igrf 数据目录(底层 Fortran 代码通过相对路径读取数据文件)
WorkingDirectory cwd{datadir.string()};
if(!cwd.isChanged()){
Expand Down
4 changes: 0 additions & 4 deletions test/weather/testMagnetosphere.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ AST_USING_NAMESPACE

TEST(Magnetosphere, igrf13)
{
if(!aIsGithubCI()) GTEST_SKIP();

printf("igrf13syn test\n");
{
Expand Down Expand Up @@ -71,7 +70,6 @@ TEST(Magnetosphere, igrf13)

TEST(Magnetosphere, aep8)
{
if(!aIsGithubCI()) GTEST_SKIP();

double flux;
{
Expand Down Expand Up @@ -121,7 +119,6 @@ TEST(Magnetosphere, aep8)

TEST(Magnetosphere, igrf)
{
if(!aIsGithubCI()) GTEST_SKIP();

{
double lon = 85;
Expand Down Expand Up @@ -150,7 +147,6 @@ TEST(Magnetosphere, igrf)

TEST(Magnetosphere, get_flux)
{
if(!aIsGithubCI()) GTEST_SKIP();

{
double lon = -45;
Expand Down
Loading