mirror of
https://github.com/OpenMW/openmw.git
synced 2025-04-14 00:36:43 +00:00
Issue #168 - Configuration cleanup
Added tokens processing, modified getInstallPath for linux so we could use ~/.wine/dosdevices symlinks. Signed-off-by: Lukasz Gromanowski <lgromanowski@gmail.com>
This commit is contained in:
parent
841bdded76
commit
1d96b99532
6 changed files with 226 additions and 83 deletions
|
@ -157,6 +157,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
||||||
engine.enableFSStrict(variables["fs-strict"].as<bool>());
|
engine.enableFSStrict(variables["fs-strict"].as<bool>());
|
||||||
|
|
||||||
Files::PathContainer dataDirs(variables["data"].as<Files::PathContainer>());
|
Files::PathContainer dataDirs(variables["data"].as<Files::PathContainer>());
|
||||||
|
cfgMgr.processPaths(dataDirs);
|
||||||
|
|
||||||
std::string local(variables["data-local"].as<std::string>());
|
std::string local(variables["data-local"].as<std::string>());
|
||||||
if (!local.empty())
|
if (!local.empty())
|
||||||
|
@ -166,7 +167,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
||||||
|
|
||||||
if (dataDirs.empty())
|
if (dataDirs.empty())
|
||||||
{
|
{
|
||||||
dataDirs.push_back(cfgMgr.getDataPath(Files::localDataToken));
|
dataDirs.push_back(cfgMgr.getLocalDataPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.setDataDirs(dataDirs);
|
engine.setDataDirs(dataDirs);
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <functional>
|
#include <algorithm>
|
||||||
|
|
||||||
namespace Files
|
namespace Files
|
||||||
{
|
{
|
||||||
|
@ -24,7 +24,7 @@ ConfigurationManager::ConfigurationManager()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* According to task #168 plugins.cfg file shall be located in global
|
* According to task #168 plugins.cfg file shall be located in global
|
||||||
* configuration path or in runtime configuration path.
|
* configuration path or in local configuration path.
|
||||||
*/
|
*/
|
||||||
mPluginsCfgPath = mFixedPath.getGlobalPath() / pluginsCfgFile;
|
mPluginsCfgPath = mFixedPath.getGlobalPath() / pluginsCfgFile;
|
||||||
if (!boost::filesystem::is_regular_file(mPluginsCfgPath))
|
if (!boost::filesystem::is_regular_file(mPluginsCfgPath))
|
||||||
|
@ -55,10 +55,10 @@ ConfigurationManager::~ConfigurationManager()
|
||||||
|
|
||||||
void ConfigurationManager::setupTokensMapping()
|
void ConfigurationManager::setupTokensMapping()
|
||||||
{
|
{
|
||||||
mTokensMapping.insert(std::make_pair(mwDataToken, &FixedPath<>::getInstallPath));
|
mTokensMapping.insert(std::make_pair(mwDataToken, &FixedPath<>::setInstallPath));
|
||||||
mTokensMapping.insert(std::make_pair(localDataToken, &FixedPath<>::getLocalDataPath));
|
mTokensMapping.insert(std::make_pair(localDataToken, &FixedPath<>::setLocalDataPath));
|
||||||
mTokensMapping.insert(std::make_pair(userDataToken, &FixedPath<>::getUserDataPath));
|
mTokensMapping.insert(std::make_pair(userDataToken, &FixedPath<>::setUserDataPath));
|
||||||
mTokensMapping.insert(std::make_pair(globalDataToken, &FixedPath<>::getGlobalDataPath));
|
mTokensMapping.insert(std::make_pair(globalDataToken, &FixedPath<>::setGlobalDataPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables,
|
void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables,
|
||||||
|
@ -66,10 +66,54 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m
|
||||||
{
|
{
|
||||||
loadConfig(mFixedPath.getUserPath(), variables, description);
|
loadConfig(mFixedPath.getUserPath(), variables, description);
|
||||||
boost::program_options::notify(variables);
|
boost::program_options::notify(variables);
|
||||||
|
|
||||||
loadConfig(mFixedPath.getLocalPath(), variables, description);
|
loadConfig(mFixedPath.getLocalPath(), variables, description);
|
||||||
boost::program_options::notify(variables);
|
boost::program_options::notify(variables);
|
||||||
loadConfig(mFixedPath.getGlobalPath(), variables, description);
|
loadConfig(mFixedPath.getGlobalPath(), variables, description);
|
||||||
boost::program_options::notify(variables);
|
boost::program_options::notify(variables);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EmptyPath : public std::unary_function<const boost::filesystem::path&, bool>
|
||||||
|
{
|
||||||
|
bool operator()(const boost::filesystem::path& path) const
|
||||||
|
{
|
||||||
|
return path.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void ConfigurationManager::processPaths(Files::PathContainer& dataDirs)
|
||||||
|
{
|
||||||
|
for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it)
|
||||||
|
{
|
||||||
|
const std::string& path = it->string();
|
||||||
|
if (!path.empty() && path[0] == '?')
|
||||||
|
{
|
||||||
|
std::string::size_type pos = path.find('?', 1);
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
++pos;
|
||||||
|
TokensMappingContainer::iterator tokenIt = mTokensMapping.find(path.substr(0, pos));
|
||||||
|
|
||||||
|
if (tokenIt != mTokensMapping.end())
|
||||||
|
{
|
||||||
|
boost::filesystem::path tempPath(path.substr(pos, path.length() - pos));
|
||||||
|
|
||||||
|
if (boost::filesystem::is_directory(tempPath))
|
||||||
|
{
|
||||||
|
((mFixedPath).*(tokenIt->second))(tempPath);
|
||||||
|
(*it) = tempPath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(*it).clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataDirs.erase(std::remove_if(dataDirs.begin(), dataDirs.end(), EmptyPath()), dataDirs.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigurationManager::loadConfig(const boost::filesystem::path& path,
|
void ConfigurationManager::loadConfig(const boost::filesystem::path& path,
|
||||||
|
@ -112,17 +156,26 @@ const boost::filesystem::path& ConfigurationManager::getLocalPath() const
|
||||||
return mFixedPath.getLocalPath();
|
return mFixedPath.getLocalPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
const boost::filesystem::path& ConfigurationManager::getDataPath(const std::string& type) const
|
const boost::filesystem::path& ConfigurationManager::getGlobalDataPath() const
|
||||||
{
|
{
|
||||||
TokensMappingContainer::const_iterator it = mTokensMapping.find(type);
|
return mFixedPath.getGlobalDataPath();
|
||||||
if (it != mTokensMapping.end())
|
}
|
||||||
{
|
|
||||||
return ((mFixedPath).*(it->second))();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const boost::filesystem::path& ConfigurationManager::getUserDataPath() const
|
||||||
|
{
|
||||||
|
return mFixedPath.getUserDataPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
const boost::filesystem::path& ConfigurationManager::getLocalDataPath() const
|
||||||
|
{
|
||||||
return mFixedPath.getLocalDataPath();
|
return mFixedPath.getLocalDataPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const boost::filesystem::path& ConfigurationManager::getInstallPath() const
|
||||||
|
{
|
||||||
|
return mFixedPath.getInstallPath();
|
||||||
|
}
|
||||||
|
|
||||||
const boost::filesystem::path& ConfigurationManager::getOgreConfigPath() const
|
const boost::filesystem::path& ConfigurationManager::getOgreConfigPath() const
|
||||||
{
|
{
|
||||||
return mOgreCfgPath;
|
return mOgreCfgPath;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
#include <components/files/fixedpath.hpp>
|
#include <components/files/fixedpath.hpp>
|
||||||
|
#include <components/files/collections.hpp>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \namespace Files
|
* \namespace Files
|
||||||
|
@ -14,12 +15,6 @@
|
||||||
namespace Files
|
namespace Files
|
||||||
{
|
{
|
||||||
|
|
||||||
extern const char* const mwDataToken;
|
|
||||||
extern const char* const localDataToken;
|
|
||||||
extern const char* const userDataToken;
|
|
||||||
extern const char* const globalDataToken;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \struct ConfigurationManager
|
* \struct ConfigurationManager
|
||||||
*/
|
*/
|
||||||
|
@ -30,13 +25,17 @@ struct ConfigurationManager
|
||||||
|
|
||||||
void readConfiguration(boost::program_options::variables_map& variables,
|
void readConfiguration(boost::program_options::variables_map& variables,
|
||||||
boost::program_options::options_description& description);
|
boost::program_options::options_description& description);
|
||||||
|
void processPaths(Files::PathContainer& dataDirs);
|
||||||
|
|
||||||
/**< Fixed paths */
|
/**< Fixed paths */
|
||||||
const boost::filesystem::path& getGlobalPath() const;
|
const boost::filesystem::path& getGlobalPath() const;
|
||||||
const boost::filesystem::path& getUserPath() const;
|
const boost::filesystem::path& getUserPath() const;
|
||||||
const boost::filesystem::path& getLocalPath() const ;
|
const boost::filesystem::path& getLocalPath() const;
|
||||||
|
|
||||||
const boost::filesystem::path& getDataPath(const std::string& type) const;
|
const boost::filesystem::path& getGlobalDataPath() const;
|
||||||
|
const boost::filesystem::path& getUserDataPath() const;
|
||||||
|
const boost::filesystem::path& getLocalDataPath() const;
|
||||||
|
const boost::filesystem::path& getInstallPath() const;
|
||||||
|
|
||||||
const boost::filesystem::path& getOgreConfigPath() const;
|
const boost::filesystem::path& getOgreConfigPath() const;
|
||||||
const boost::filesystem::path& getPluginsConfigPath() const;
|
const boost::filesystem::path& getPluginsConfigPath() const;
|
||||||
|
@ -45,7 +44,7 @@ struct ConfigurationManager
|
||||||
private:
|
private:
|
||||||
typedef Files::FixedPath<> FixedPathType;
|
typedef Files::FixedPath<> FixedPathType;
|
||||||
|
|
||||||
typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const;
|
typedef void (FixedPathType::*path_type_f)(const boost::filesystem::path&);
|
||||||
typedef std::tr1::unordered_map<std::string, path_type_f> TokensMappingContainer;
|
typedef std::tr1::unordered_map<std::string, path_type_f> TokensMappingContainer;
|
||||||
|
|
||||||
void loadConfig(const boost::filesystem::path& path,
|
void loadConfig(const boost::filesystem::path& path,
|
||||||
|
|
|
@ -122,11 +122,12 @@ struct FixedPath
|
||||||
|
|
||||||
const boost::filesystem::path& getInstallPath() const
|
const boost::filesystem::path& getInstallPath() const
|
||||||
{
|
{
|
||||||
// TODO: It will be corrected later.
|
return mInstallPath;
|
||||||
static boost::filesystem::path p("./");
|
}
|
||||||
return p;
|
|
||||||
|
|
||||||
//return mFixedPath.getInstallPath();
|
void setInstallPath(const boost::filesystem::path& path)
|
||||||
|
{
|
||||||
|
mInstallPath = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
const boost::filesystem::path& getGlobalDataPath() const
|
const boost::filesystem::path& getGlobalDataPath() const
|
||||||
|
@ -134,16 +135,31 @@ struct FixedPath
|
||||||
return mGlobalDataPath;
|
return mGlobalDataPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setGlobalDataPath(const boost::filesystem::path& path)
|
||||||
|
{
|
||||||
|
mGlobalDataPath = path;
|
||||||
|
}
|
||||||
|
|
||||||
const boost::filesystem::path& getUserDataPath() const
|
const boost::filesystem::path& getUserDataPath() const
|
||||||
{
|
{
|
||||||
return mUserDataPath;
|
return mUserDataPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setUserDataPath(const boost::filesystem::path& path)
|
||||||
|
{
|
||||||
|
mUserDataPath = path;
|
||||||
|
}
|
||||||
|
|
||||||
const boost::filesystem::path& getLocalDataPath() const
|
const boost::filesystem::path& getLocalDataPath() const
|
||||||
{
|
{
|
||||||
return mLocalDataPath;
|
return mLocalDataPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setLocalDataPath(const boost::filesystem::path& path)
|
||||||
|
{
|
||||||
|
mLocalDataPath = path;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PathType mPath;
|
PathType mPath;
|
||||||
|
|
||||||
|
|
|
@ -157,73 +157,75 @@ boost::filesystem::path LinuxPath::getLocalDataPath() const
|
||||||
|
|
||||||
boost::filesystem::path LinuxPath::getInstallPath() const
|
boost::filesystem::path LinuxPath::getInstallPath() const
|
||||||
{
|
{
|
||||||
char *homePath = getenv("HOME");
|
boost::filesystem::path installPath;
|
||||||
if(!homePath)
|
|
||||||
{
|
|
||||||
return boost::filesystem::path("");
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::filesystem::path wineDefaultRegistry(homePath);
|
|
||||||
wineDefaultRegistry /= ".wine/system.reg";
|
|
||||||
|
|
||||||
boost::filesystem::path wineDriveC(homePath);
|
|
||||||
wineDriveC /= ".wine/drive_c";
|
|
||||||
|
|
||||||
boost::filesystem::file_status fileStatus = boost::filesystem::status(wineDefaultRegistry);
|
char *homePath = getenv("HOME");
|
||||||
boost::filesystem::file_status dirStatus = boost::filesystem::status(wineDriveC);
|
if (homePath == NULL)
|
||||||
if(!boost::filesystem::is_regular_file(fileStatus) || !boost::filesystem::is_directory(dirStatus))
|
|
||||||
{
|
{
|
||||||
return boost::filesystem::path("");
|
struct passwd* pwd = getpwuid(getuid());
|
||||||
}
|
if (pwd != NULL)
|
||||||
|
|
||||||
|
|
||||||
boost::filesystem::ifstream file(wineDefaultRegistry);
|
|
||||||
bool isRegEntry = false;
|
|
||||||
std::string line;
|
|
||||||
|
|
||||||
while (std::getline(file, line))
|
|
||||||
{
|
|
||||||
if(!line.empty() && line[0] == '[') // we found an entry
|
|
||||||
{
|
{
|
||||||
std::string regkey = line.substr(1, line.find(']')-1);
|
homePath = pwd->pw_dir;
|
||||||
if( regkey.compare("SOFTWARE\\\\Wow6432Node\\\\Bethesda Softworks\\\\Morrowind") == 0
|
|
||||||
|| regkey.compare("SOFTWARE\\\\Bethesda Softworks\\\\Morrowind") == 0 )
|
|
||||||
{
|
|
||||||
isRegEntry = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if(isRegEntry)
|
}
|
||||||
|
|
||||||
|
if (homePath != NULL)
|
||||||
|
{
|
||||||
|
boost::filesystem::path wineDefaultRegistry(homePath);
|
||||||
|
wineDefaultRegistry /= ".wine/system.reg";
|
||||||
|
|
||||||
|
if (boost::filesystem::is_regular_file(wineDefaultRegistry))
|
||||||
{
|
{
|
||||||
if(line.empty() || line[0] != '"') // empty line means new registry key
|
boost::filesystem::ifstream file(wineDefaultRegistry);
|
||||||
|
bool isRegEntry = false;
|
||||||
|
std::string line;
|
||||||
|
std::string mwpath;
|
||||||
|
|
||||||
|
while (std::getline(file, line) && !line.empty())
|
||||||
{
|
{
|
||||||
break;
|
if (line[0] == '[') // we found an entry
|
||||||
}
|
|
||||||
std::string key = line.substr(1, line.find('"', 1)-1);
|
|
||||||
if(key.compare("Installed Path") == 0) {
|
|
||||||
std::string::size_type pos, startPos;
|
|
||||||
|
|
||||||
startPos = line.find('=')+2;
|
|
||||||
std::string installPath = line.substr(startPos, line.find('"', startPos+1)-startPos);
|
|
||||||
installPath.replace(0, 2, wineDriveC.string());
|
|
||||||
|
|
||||||
pos = -1;
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
pos = installPath.find("\\\\", pos+1);
|
isRegEntry = (line.find("Softworks\\Morrowind]") != std::string::npos);
|
||||||
if(pos == std::string::npos)
|
}
|
||||||
|
else if (isRegEntry)
|
||||||
|
{
|
||||||
|
if (line[0] == '"') // empty line means new registry key
|
||||||
{
|
{
|
||||||
break;
|
std::string key = line.substr(1, line.find('"', 1) - 1);
|
||||||
|
if (strcasecmp(key.c_str(), "Installed Path") == 0)
|
||||||
|
{
|
||||||
|
std::string::size_type valuePos = line.find('=') + 2;
|
||||||
|
mwpath = line.substr(valuePos, line.rfind('"') - valuePos);
|
||||||
|
|
||||||
|
std::string::size_type pos = mwpath.find("\\");
|
||||||
|
while (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
mwpath.replace(pos, 2, "/");
|
||||||
|
pos = mwpath.find("\\", pos + 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
installPath.replace(pos, 2, "/");
|
}
|
||||||
} while(true);
|
|
||||||
|
if (!mwpath.empty())
|
||||||
return boost::filesystem::path(installPath);
|
{
|
||||||
|
// Change drive letter to lowercase, so we could use ~/.wine/dosdevice symlinks
|
||||||
|
mwpath[0] = tolower(mwpath[0]);
|
||||||
|
installPath /= homePath;
|
||||||
|
installPath /= ".wine/dosdevices/";
|
||||||
|
installPath /= mwpath;
|
||||||
|
|
||||||
|
if (!boost::filesystem::is_directory(installPath))
|
||||||
|
{
|
||||||
|
installPath.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return boost::filesystem::path("");
|
return installPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace Files */
|
} /* namespace Files */
|
||||||
|
|
|
@ -112,11 +112,83 @@ boost::filesystem::path MacOsPath::getLocalDataPath() const
|
||||||
return boost::filesystem::path("./data/");
|
return boost::filesystem::path("./data/");
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::filesystem::path MacOsPath::getInstallPath() const;
|
/**
|
||||||
|
* FIXME: This should be verified on MacOS system!
|
||||||
|
*/
|
||||||
|
boost::filesystem::path MacOsPath::getInstallPath() const
|
||||||
{
|
{
|
||||||
return boost::filesystem::path("./");
|
boost::filesystem::path installPath;
|
||||||
|
|
||||||
|
char *homePath = getenv("HOME");
|
||||||
|
if (homePath == NULL)
|
||||||
|
{
|
||||||
|
struct passwd* pwd = getpwuid(getuid());
|
||||||
|
if (pwd != NULL)
|
||||||
|
{
|
||||||
|
homePath = pwd->pw_dir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (homePath != NULL)
|
||||||
|
{
|
||||||
|
boost::filesystem::path wineDefaultRegistry(homePath);
|
||||||
|
wineDefaultRegistry /= ".wine/system.reg";
|
||||||
|
|
||||||
|
if (boost::filesystem::is_regular_file(wineDefaultRegistry))
|
||||||
|
{
|
||||||
|
boost::filesystem::ifstream file(wineDefaultRegistry);
|
||||||
|
bool isRegEntry = false;
|
||||||
|
std::string line;
|
||||||
|
std::string mwpath;
|
||||||
|
|
||||||
|
while (std::getline(file, line) && !line.empty())
|
||||||
|
{
|
||||||
|
if (line[0] == '[') // we found an entry
|
||||||
|
{
|
||||||
|
isRegEntry = (line.find("Softworks\\Morrowind]") != std::string::npos);
|
||||||
|
}
|
||||||
|
else if (isRegEntry)
|
||||||
|
{
|
||||||
|
if (line[0] == '"') // empty line means new registry key
|
||||||
|
{
|
||||||
|
std::string key = line.substr(1, line.find('"', 1) - 1);
|
||||||
|
if (strcasecmp(key.c_str(), "Installed Path") == 0)
|
||||||
|
{
|
||||||
|
std::string::size_type valuePos = line.find('=') + 2;
|
||||||
|
mwpath = line.substr(valuePos, line.rfind('"') - valuePos);
|
||||||
|
|
||||||
|
std::string::size_type pos = mwpath.find("\\");
|
||||||
|
while (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
mwpath.replace(pos, 2, "/");
|
||||||
|
pos = mwpath.find("\\", pos + 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mwpath.empty())
|
||||||
|
{
|
||||||
|
// Change drive letter to lowercase, so we could use ~/.wine/dosdevice symlinks
|
||||||
|
mwpath[0] = tolower(mwpath[0]);
|
||||||
|
installPath /= homePath;
|
||||||
|
installPath /= ".wine/dosdevices/";
|
||||||
|
installPath /= mwpath;
|
||||||
|
|
||||||
|
if (!boost::filesystem::is_directory(installPath))
|
||||||
|
{
|
||||||
|
installPath.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return installPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} /* namespace Files */
|
} /* namespace Files */
|
||||||
|
|
||||||
#endif /* defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) */
|
#endif /* defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) */
|
||||||
|
|
Loading…
Reference in a new issue