Fix T93960: Asset Catalogs I/O fails with unicode file paths on Windows
On Windows, encode file paths as UTF-16 before trying to open the file for reading/writing. This introduces a new class `blender::fstream`, which wraps `std::fstream` and provides this UTF-16 encoding. This class should also be used in other areas, like the Alembic importer/exporter. Manifest Task: T93960 Reviewed By: JacquesLucke Differential Revision: https://developer.blender.org/D13633
This commit is contained in:
@@ -24,7 +24,7 @@
|
|||||||
#include "BKE_asset_catalog.hh"
|
#include "BKE_asset_catalog.hh"
|
||||||
#include "BKE_asset_library.h"
|
#include "BKE_asset_library.h"
|
||||||
|
|
||||||
#include "BLI_fileops.h"
|
#include "BLI_fileops.hh"
|
||||||
#include "BLI_path_util.h"
|
#include "BLI_path_util.h"
|
||||||
|
|
||||||
/* For S_ISREG() and S_ISDIR() on Windows. */
|
/* For S_ISREG() and S_ISDIR() on Windows. */
|
||||||
@@ -830,7 +830,7 @@ void AssetCatalogDefinitionFile::parse_catalog_file(
|
|||||||
const CatalogFilePath &catalog_definition_file_path,
|
const CatalogFilePath &catalog_definition_file_path,
|
||||||
AssetCatalogParsedFn catalog_loaded_callback)
|
AssetCatalogParsedFn catalog_loaded_callback)
|
||||||
{
|
{
|
||||||
std::fstream infile(catalog_definition_file_path);
|
fstream infile(catalog_definition_file_path, std::ios::in);
|
||||||
|
|
||||||
if (!infile.is_open()) {
|
if (!infile.is_open()) {
|
||||||
CLOG_ERROR(&LOG, "%s: unable to open file", catalog_definition_file_path.c_str());
|
CLOG_ERROR(&LOG, "%s: unable to open file", catalog_definition_file_path.c_str());
|
||||||
@@ -966,7 +966,7 @@ bool AssetCatalogDefinitionFile::write_to_disk_unsafe(const CatalogFilePath &des
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream output(dest_file_path);
|
fstream output(dest_file_path, std::ios::out);
|
||||||
|
|
||||||
/* TODO(@sybren): remember the line ending style that was originally read, then use that to write
|
/* TODO(@sybren): remember the line ending style that was originally read, then use that to write
|
||||||
* the file again. */
|
* the file again. */
|
||||||
|
@@ -563,6 +563,30 @@ TEST_F(AssetCatalogTest, write_single_file)
|
|||||||
/* TODO(@sybren): test ordering of catalogs in the file. */
|
/* TODO(@sybren): test ordering of catalogs in the file. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(AssetCatalogTest, read_write_unicode_filepath)
|
||||||
|
{
|
||||||
|
TestableAssetCatalogService service(asset_library_root_);
|
||||||
|
const CatalogFilePath load_from_path = asset_library_root_ + "/новый/" +
|
||||||
|
AssetCatalogService::DEFAULT_CATALOG_FILENAME;
|
||||||
|
service.load_from_disk(load_from_path);
|
||||||
|
|
||||||
|
const CatalogFilePath save_to_path = use_temp_path() + "новый.cats.txt";
|
||||||
|
AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file();
|
||||||
|
ASSERT_NE(nullptr, cdf) << "unable to load " << load_from_path;
|
||||||
|
EXPECT_TRUE(cdf->write_to_disk(save_to_path));
|
||||||
|
|
||||||
|
AssetCatalogService loaded_service(save_to_path);
|
||||||
|
loaded_service.load_from_disk();
|
||||||
|
|
||||||
|
/* Test that the file was loaded correctly. */
|
||||||
|
const bUUID materials_uuid("a2151dff-dead-4f29-b6bc-b2c7d6cccdb4");
|
||||||
|
const AssetCatalog *cat = loaded_service.find_catalog(materials_uuid);
|
||||||
|
ASSERT_NE(nullptr, cat);
|
||||||
|
EXPECT_EQ(materials_uuid, cat->catalog_id);
|
||||||
|
EXPECT_EQ(AssetCatalogPath("Материалы"), cat->path);
|
||||||
|
EXPECT_EQ("Russian Materials", cat->simple_name);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(AssetCatalogTest, no_writing_empty_files)
|
TEST_F(AssetCatalogTest, no_writing_empty_files)
|
||||||
{
|
{
|
||||||
const CatalogFilePath temp_lib_root = create_temp_path();
|
const CatalogFilePath temp_lib_root = create_temp_path();
|
||||||
|
52
source/blender/blenlib/BLI_fileops.hh
Normal file
52
source/blender/blenlib/BLI_fileops.hh
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup bli
|
||||||
|
* \brief File and directory operations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __cplusplus
|
||||||
|
# error This is a C++ header
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "BLI_fileops.h"
|
||||||
|
#include "BLI_string_ref.hh"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace blender {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* std::fstream subclass that handles UTF-16 encoding on Windows.
|
||||||
|
*
|
||||||
|
* For documentation, see https://en.cppreference.com/w/cpp/io/basic_fstream
|
||||||
|
*/
|
||||||
|
class fstream : public std::fstream {
|
||||||
|
public:
|
||||||
|
fstream() = default;
|
||||||
|
explicit fstream(const char *filepath,
|
||||||
|
std::ios_base::openmode mode = ios_base::in | ios_base::out);
|
||||||
|
explicit fstream(const std::string &filepath,
|
||||||
|
std::ios_base::openmode mode = ios_base::in | ios_base::out);
|
||||||
|
|
||||||
|
void open(StringRefNull filepath, ios_base::openmode mode = ios_base::in | ios_base::out);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace blender
|
@@ -76,6 +76,7 @@ set(SRC
|
|||||||
intern/endian_switch.c
|
intern/endian_switch.c
|
||||||
intern/expr_pylike_eval.c
|
intern/expr_pylike_eval.c
|
||||||
intern/fileops.c
|
intern/fileops.c
|
||||||
|
intern/fileops.cc
|
||||||
intern/filereader_file.c
|
intern/filereader_file.c
|
||||||
intern/filereader_gzip.c
|
intern/filereader_gzip.c
|
||||||
intern/filereader_memory.c
|
intern/filereader_memory.c
|
||||||
@@ -204,6 +205,7 @@ set(SRC
|
|||||||
BLI_enumerable_thread_specific.hh
|
BLI_enumerable_thread_specific.hh
|
||||||
BLI_expr_pylike_eval.h
|
BLI_expr_pylike_eval.h
|
||||||
BLI_fileops.h
|
BLI_fileops.h
|
||||||
|
BLI_fileops.hh
|
||||||
BLI_fileops_types.h
|
BLI_fileops_types.h
|
||||||
BLI_filereader.h
|
BLI_filereader.h
|
||||||
BLI_float2.hh
|
BLI_float2.hh
|
||||||
@@ -422,6 +424,7 @@ if(WITH_GTESTS)
|
|||||||
tests/BLI_edgehash_test.cc
|
tests/BLI_edgehash_test.cc
|
||||||
tests/BLI_expr_pylike_eval_test.cc
|
tests/BLI_expr_pylike_eval_test.cc
|
||||||
tests/BLI_function_ref_test.cc
|
tests/BLI_function_ref_test.cc
|
||||||
|
tests/BLI_fileops_test.cc
|
||||||
tests/BLI_ghash_test.cc
|
tests/BLI_ghash_test.cc
|
||||||
tests/BLI_hash_mm2a_test.cc
|
tests/BLI_hash_mm2a_test.cc
|
||||||
tests/BLI_heap_simple_test.cc
|
tests/BLI_heap_simple_test.cc
|
||||||
|
51
source/blender/blenlib/intern/fileops.cc
Normal file
51
source/blender/blenlib/intern/fileops.cc
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup bli
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "BLI_fileops.hh"
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
# include "utfconv.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace blender {
|
||||||
|
fstream::fstream(const char *filepath, std::ios_base::openmode mode)
|
||||||
|
{
|
||||||
|
this->open(filepath, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
fstream::fstream(const std::string &filepath, std::ios_base::openmode mode)
|
||||||
|
{
|
||||||
|
this->open(filepath, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fstream::open(StringRefNull filepath, ios_base::openmode mode)
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
const char *filepath_cstr = filepath.c_str();
|
||||||
|
UTF16_ENCODE(filepath_cstr);
|
||||||
|
std::wstring filepath_wstr(filepath_cstr_16);
|
||||||
|
std::fstream::open(filepath_wstr.c_str(), mode);
|
||||||
|
UTF16_UN_ENCODE(filepath_cstr);
|
||||||
|
#else
|
||||||
|
std::fstream::open(filepath, mode);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender
|
40
source/blender/blenlib/tests/BLI_fileops_test.cc
Normal file
40
source/blender/blenlib/tests/BLI_fileops_test.cc
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/* Apache License, Version 2.0 */
|
||||||
|
|
||||||
|
#include "BLI_fileops.hh"
|
||||||
|
|
||||||
|
#include "testing/testing.h"
|
||||||
|
|
||||||
|
namespace blender::tests {
|
||||||
|
|
||||||
|
TEST(fileops, fstream_open_string_filename)
|
||||||
|
{
|
||||||
|
const std::string test_files_dir = blender::tests::flags_test_asset_dir();
|
||||||
|
if (test_files_dir.empty()) {
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string filepath = test_files_dir + "/asset_library/новый/blender_assets.cats.txt";
|
||||||
|
fstream in(filepath, std::ios_base::in);
|
||||||
|
ASSERT_TRUE(in.is_open()) << "could not open " << filepath;
|
||||||
|
in.close(); /* This should not crash. */
|
||||||
|
|
||||||
|
/* Reading the file not tested here. That's deferred to `std::fstream` anyway. */
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(fileops, fstream_open_charptr_filename)
|
||||||
|
{
|
||||||
|
const std::string test_files_dir = blender::tests::flags_test_asset_dir();
|
||||||
|
if (test_files_dir.empty()) {
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string filepath_str = test_files_dir + "/asset_library/новый/blender_assets.cats.txt";
|
||||||
|
const char *filepath = filepath_str.c_str();
|
||||||
|
fstream in(filepath, std::ios_base::in);
|
||||||
|
ASSERT_TRUE(in.is_open()) << "could not open " << filepath;
|
||||||
|
in.close(); /* This should not crash. */
|
||||||
|
|
||||||
|
/* Reading the file not tested here. That's deferred to `std::fstream` anyway. */
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::tests
|
Reference in New Issue
Block a user