add audio libraries, and download transitive package requirements
This commit is contained in:
parent
787bfc5f94
commit
d7b2de8029
|
|
@ -238,16 +238,39 @@ class AppRunner(DirectObject):
|
|||
packageName, version, hostUrl)
|
||||
return False
|
||||
|
||||
return self.__rInstallPackage(package, [])
|
||||
|
||||
def __rInstallPackage(self, package, nested):
|
||||
""" The recursive implementation of installPackage(). The new
|
||||
parameter, nested, is a list of packages that we are
|
||||
recursively calling this from, to avoid recursive loops. """
|
||||
|
||||
if not package.downloadDescFile(self.http):
|
||||
return False
|
||||
|
||||
# Now that we've downloaded and read the desc file, we can
|
||||
# install all of the required packages first.
|
||||
nested = nested[:] + [self]
|
||||
for packageName, version, host in package.requires:
|
||||
if host.downloadContentsFile(self.http):
|
||||
p2 = host.getPackage(packageName, version)
|
||||
if not p2:
|
||||
print "Couldn't find %s %s on %s" % (packageName, version, host.hostUrl)
|
||||
else:
|
||||
if p2 not in nested:
|
||||
self.__rInstallPackage(p2, nested)
|
||||
|
||||
# Now that all of the required packages are installed, carry
|
||||
# on to download and install this package.
|
||||
if not package.downloadPackage(self.http):
|
||||
return False
|
||||
|
||||
if not package.installPackage(self):
|
||||
return False
|
||||
|
||||
print "Package %s %s installed." % (packageName, version)
|
||||
print "Package %s %s installed." % (
|
||||
package.packageName, package.packageVersion)
|
||||
return True
|
||||
|
||||
def getHostWithAlt(self, hostUrl):
|
||||
""" Returns a suitable HostInfo object for downloading
|
||||
|
|
@ -290,7 +313,6 @@ class AppRunner(DirectObject):
|
|||
mapped host, which is the one we should actually download
|
||||
from, see getHostWithAlt(). """
|
||||
|
||||
|
||||
if hostUrl is None:
|
||||
hostUrl = PandaSystem.getPackageHostUrl()
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ class PackageInfo:
|
|||
self.uncompressedArchive = None
|
||||
self.compressedArchive = None
|
||||
self.extracts = []
|
||||
self.requires = []
|
||||
self.installPlans = None
|
||||
|
||||
# This is updated during downloadPackage(). It is in the
|
||||
|
|
@ -253,6 +254,7 @@ class PackageInfo:
|
|||
self.compressedArchive.loadXml(xcompressedArchive)
|
||||
|
||||
# The list of files that should be extracted to disk.
|
||||
self.extracts = []
|
||||
xextract = xpackage.FirstChildElement('extract')
|
||||
while xextract:
|
||||
file = FileSpec()
|
||||
|
|
@ -260,6 +262,19 @@ class PackageInfo:
|
|||
self.extracts.append(file)
|
||||
xextract = xextract.NextSiblingElement('extract')
|
||||
|
||||
# The list of additional packages that must be installed for
|
||||
# this package to function properly.
|
||||
self.requires = []
|
||||
xrequires = xpackage.FirstChildElement('requires')
|
||||
while xrequires:
|
||||
packageName = xrequires.Attribute('name')
|
||||
version = xrequires.Attribute('version')
|
||||
hostUrl = xrequires.Attribute('host')
|
||||
if packageName and hostUrl:
|
||||
host = self.host.appRunner.getHostWithAlt(hostUrl)
|
||||
self.requires.append((packageName, version, host))
|
||||
xrequires = xrequires.NextSiblingElement('requires')
|
||||
|
||||
self.hasDescFile = True
|
||||
|
||||
# Now that we've read the desc file, go ahead and use it to
|
||||
|
|
|
|||
|
|
@ -76,6 +76,13 @@ class PackageInstaller(DirectObject):
|
|||
# getDescFile().
|
||||
self.downloadEffort = 0
|
||||
|
||||
def __cmp__(self, pp):
|
||||
""" Python comparision function. This makes all
|
||||
PendingPackages withe same (packageName, version, host)
|
||||
combination be deemed equivalent. """
|
||||
return cmp((self.packageName, self.version, self.host),
|
||||
(pp.packageName, pp.version, pp.host))
|
||||
|
||||
def getProgress(self):
|
||||
""" Returns the download progress of this package in the
|
||||
range 0..1. """
|
||||
|
|
@ -230,16 +237,37 @@ class PackageInstaller(DirectObject):
|
|||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
self.packages.append(pp)
|
||||
if not pp.checkDescFile():
|
||||
# Still need to download the desc file.
|
||||
self.needsDescFile.append(pp)
|
||||
if not self.descFileTask:
|
||||
self.descFileTask = taskMgr.add(
|
||||
self.__getDescFileTask, 'getDescFile',
|
||||
taskChain = self.taskChain)
|
||||
self.__internalAddPackage(pp)
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
elif not pp.package.hasPackage:
|
||||
def __internalAddPackage(self, pp):
|
||||
""" Adds the indicated "pending package" to the appropriate
|
||||
list(s) for downloading and installing. Assumes packageLock
|
||||
is already held."""
|
||||
|
||||
if pp in self.packages:
|
||||
# Already added.
|
||||
return
|
||||
|
||||
self.packages.append(pp)
|
||||
if not pp.checkDescFile():
|
||||
# Still need to download the desc file.
|
||||
self.needsDescFile.append(pp)
|
||||
if not self.descFileTask:
|
||||
self.descFileTask = taskMgr.add(
|
||||
self.__getDescFileTask, 'getDescFile',
|
||||
taskChain = self.taskChain)
|
||||
|
||||
else:
|
||||
# The desc file is ready, which means so are the requirements.
|
||||
for packageName, version, host in pp.package.requires:
|
||||
pp2 = self.PendingPackage(packageName, version, host)
|
||||
self.__internalAddPackage(pp2)
|
||||
|
||||
# Now that we've added the required packages, add the
|
||||
# package itself.
|
||||
if not pp.package.hasPackage:
|
||||
# The desc file is good, but the package itself needs
|
||||
# to be downloaded.
|
||||
self.needsDownload.append(pp)
|
||||
|
|
@ -247,9 +275,6 @@ class PackageInstaller(DirectObject):
|
|||
else:
|
||||
# The package is already fully downloaded.
|
||||
self.earlyDone.append(pp)
|
||||
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
def donePackages(self):
|
||||
""" After calling addPackage() for each package to be
|
||||
|
|
@ -494,6 +519,10 @@ class PackageInstaller(DirectObject):
|
|||
# This package is now ready to be downloaded.
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
# Also add any packages required by this one.
|
||||
for packageName, version, host in pp.package.requires:
|
||||
pp2 = self.PendingPackage(packageName, version, host)
|
||||
self.__internalAddPackage(pp2)
|
||||
self.needsDownload.append(pp)
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
|
|
|||
|
|
@ -1545,15 +1545,15 @@ class Packager:
|
|||
def requirePackage(self, package):
|
||||
""" Indicates a dependency on the given package. This
|
||||
also implicitly requires all of the package's requirements
|
||||
as well. """
|
||||
as well (though this transitive requirement happens at
|
||||
runtime, not here at build time). """
|
||||
|
||||
for p2 in package.requires + [package]:
|
||||
if p2 not in self.requires:
|
||||
self.requires.append(p2)
|
||||
for filename in p2.targetFilenames.keys():
|
||||
self.skipFilenames[filename] = True
|
||||
for moduleName, mdef in p2.moduleNames.items():
|
||||
self.skipModules[moduleName] = mdef
|
||||
if package not in self.requires:
|
||||
self.requires.append(package)
|
||||
for filename in package.targetFilenames.keys():
|
||||
self.skipFilenames[filename] = True
|
||||
for moduleName, mdef in package.moduleNames.items():
|
||||
self.skipModules[moduleName] = mdef
|
||||
|
||||
# Packager constructor
|
||||
def __init__(self):
|
||||
|
|
|
|||
|
|
@ -99,7 +99,43 @@ aux-display tinydisplay
|
|||
plugin-path $PANDA3D_ROOT
|
||||
default-model-extension .bam
|
||||
""" + auxDisplays)
|
||||
|
||||
class fmod(package):
|
||||
# This package includes the FMod audio library. This is
|
||||
# full-featured and robust, but it is closed-source and the
|
||||
# licensing is cost-free only for non-commercial products.
|
||||
|
||||
config(display_name = "FMod audio library")
|
||||
require('panda3d')
|
||||
|
||||
file('libp3fmod_audio.dll')
|
||||
|
||||
file('fmod.prc', extract = True, text = """
|
||||
plugin-path $FMOD_ROOT
|
||||
audio-library-name p3fmod_audio
|
||||
""")
|
||||
|
||||
class openal(package):
|
||||
# This package includes the OpenAL audio libraries. This is free
|
||||
# in both senses, but there are some technical issues, especially
|
||||
# on certain platforms.
|
||||
|
||||
config(display_name = "OpenAL audio library")
|
||||
require('panda3d')
|
||||
|
||||
file('libp3openal_audio.dll')
|
||||
|
||||
file('openal.prc', extract = True, text = """
|
||||
plugin-path $OPENAL_ROOT
|
||||
audio-library-name p3openal_audio
|
||||
""")
|
||||
|
||||
class audio(package):
|
||||
# This package includes the best audio library for the given platform.
|
||||
if platform.startswith('osx'):
|
||||
require('fmod')
|
||||
else:
|
||||
require('openal')
|
||||
|
||||
class egg(package):
|
||||
# This package contains the code for reading and operating on egg
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ P3DInstance(P3D_request_ready_func *func,
|
|||
_got_fparams = false;
|
||||
_got_wparams = false;
|
||||
_p3d_trusted = false;
|
||||
_xpackage = NULL;
|
||||
_p3dcert_package = NULL;
|
||||
|
||||
_fparams.set_tokens(tokens, num_tokens);
|
||||
|
|
@ -224,6 +225,11 @@ P3DInstance::
|
|||
delete _temp_p3d_filename;
|
||||
_temp_p3d_filename = NULL;
|
||||
}
|
||||
|
||||
if (_xpackage != NULL) {
|
||||
delete _xpackage;
|
||||
_xpackage = NULL;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (_frame_timer != NULL) {
|
||||
|
|
@ -761,6 +767,33 @@ handle_event(P3D_event_data event) {
|
|||
return retval;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: P3DInstance::add_package
|
||||
// Access: Public
|
||||
// Description: Adds the package to the list of packages used by this
|
||||
// instance. The instance will share responsibility for
|
||||
// downloading the package with any of the other
|
||||
// instances that use the same package.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void P3DInstance::
|
||||
add_package(const string &name, const string &version, P3DHost *host) {
|
||||
string alt_host = _fparams.lookup_token("alt_host");
|
||||
|
||||
// Look up in the p3d_info.xml file to see if this p3d file has
|
||||
// a specific alt_host indication for this host_url.
|
||||
string alt_host_url = find_alt_host_url(host->get_host_url(), alt_host);
|
||||
if (!alt_host_url.empty()) {
|
||||
// If it does, we go ahead and switch to that host now,
|
||||
// instead of bothering to contact the original host.
|
||||
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
||||
host = inst_mgr->get_host(alt_host_url);
|
||||
alt_host.clear();
|
||||
}
|
||||
|
||||
P3DPackage *package = host->get_package(name, version, alt_host);
|
||||
add_package(package);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: P3DInstance::add_package
|
||||
// Access: Public
|
||||
|
|
@ -1259,13 +1292,15 @@ scan_app_desc_file(TiXmlDocument *doc) {
|
|||
if (xpackage == NULL) {
|
||||
return;
|
||||
}
|
||||
assert(_xpackage == NULL);
|
||||
_xpackage = (TiXmlElement *)xpackage->Clone();
|
||||
|
||||
const char *log_basename = xpackage->Attribute("log_basename");
|
||||
const char *log_basename = _xpackage->Attribute("log_basename");
|
||||
if (log_basename != NULL) {
|
||||
_log_basename = log_basename;
|
||||
}
|
||||
|
||||
TiXmlElement *xconfig = xpackage->FirstChildElement("config");
|
||||
TiXmlElement *xconfig = _xpackage->FirstChildElement("config");
|
||||
if (xconfig != NULL) {
|
||||
int hidden = 0;
|
||||
if (xconfig->QueryIntAttribute("hidden", &hidden) == TIXML_SUCCESS) {
|
||||
|
|
@ -1308,9 +1343,7 @@ scan_app_desc_file(TiXmlDocument *doc) {
|
|||
_wparams.set_window_type(P3D_WT_hidden);
|
||||
}
|
||||
|
||||
string alt_host = _fparams.lookup_token("alt_host");
|
||||
|
||||
TiXmlElement *xrequires = xpackage->FirstChildElement("requires");
|
||||
TiXmlElement *xrequires = _xpackage->FirstChildElement("requires");
|
||||
while (xrequires != NULL) {
|
||||
const char *name = xrequires->Attribute("name");
|
||||
const char *host_url = xrequires->Attribute("host");
|
||||
|
|
@ -1320,20 +1353,7 @@ scan_app_desc_file(TiXmlDocument *doc) {
|
|||
version = "";
|
||||
}
|
||||
P3DHost *host = inst_mgr->get_host(host_url);
|
||||
string this_alt_host = alt_host;
|
||||
|
||||
// Look up in the p3d_info.xml file to see if this p3d file has
|
||||
// a specific alt_host indication for this host_url.
|
||||
string alt_host_url = find_alt_host_url(xpackage, host_url, alt_host);
|
||||
if (!alt_host_url.empty()) {
|
||||
// If it does, we go ahead and switch to that host now,
|
||||
// instead of bothering to contact the original host.
|
||||
host = inst_mgr->get_host(alt_host_url);
|
||||
this_alt_host.clear();
|
||||
}
|
||||
|
||||
P3DPackage *package = host->get_package(name, version, this_alt_host);
|
||||
add_package(package);
|
||||
add_package(name, version, host);
|
||||
}
|
||||
|
||||
xrequires = xrequires->NextSiblingElement("requires");
|
||||
|
|
@ -1348,9 +1368,8 @@ scan_app_desc_file(TiXmlDocument *doc) {
|
|||
// Returns empty string if there is no match.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
string P3DInstance::
|
||||
find_alt_host_url(TiXmlElement *xpackage,
|
||||
const string &host_url, const string &alt_host) {
|
||||
TiXmlElement *xhost = xpackage->FirstChildElement("host");
|
||||
find_alt_host_url(const string &host_url, const string &alt_host) {
|
||||
TiXmlElement *xhost = _xpackage->FirstChildElement("host");
|
||||
while (xhost != NULL) {
|
||||
const char *url = xhost->Attribute("url");
|
||||
if (url != NULL && host_url == url) {
|
||||
|
|
@ -1845,6 +1864,14 @@ report_package_info_ready(P3DPackage *package) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Now that the package's info is ready, we know its set of required
|
||||
// packages, and we can add these to the list.
|
||||
P3DPackage::Requires::const_iterator ri;
|
||||
for (ri = package->_requires.begin(); ri != package->_requires.end(); ++ri) {
|
||||
const P3DPackage::RequiredPackage &rp = (*ri);
|
||||
add_package(rp._package_name, rp._package_version, rp._host);
|
||||
}
|
||||
|
||||
if (get_packages_info_ready()) {
|
||||
// All packages are ready to go. Let's start some download
|
||||
// action.
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ public:
|
|||
|
||||
inline P3D_request_ready_func *get_request_ready_func() const;
|
||||
|
||||
void add_package(const string &name, const string &version, P3DHost *host);
|
||||
void add_package(P3DPackage *package);
|
||||
bool get_packages_info_ready() const;
|
||||
bool get_packages_ready() const;
|
||||
|
|
@ -154,8 +155,7 @@ private:
|
|||
void mark_p3d_untrusted();
|
||||
void mark_p3d_trusted();
|
||||
void scan_app_desc_file(TiXmlDocument *doc);
|
||||
string find_alt_host_url(TiXmlElement *xpackage,
|
||||
const string &host_url, const string &alt_host);
|
||||
string find_alt_host_url(const string &host_url, const string &alt_host);
|
||||
|
||||
void send_browser_script_object();
|
||||
P3D_request *make_p3d_request(TiXmlElement *xrequest);
|
||||
|
|
@ -218,6 +218,7 @@ private:
|
|||
P3DWindowParams _wparams;
|
||||
|
||||
bool _p3d_trusted;
|
||||
TiXmlElement *_xpackage;
|
||||
|
||||
// For downloading the p3dcert authorization program.
|
||||
P3DPackage *_p3dcert_package;
|
||||
|
|
|
|||
|
|
@ -202,3 +202,18 @@ inline void P3DPackage::InstallStep::
|
|||
report_step_progress() {
|
||||
_package->report_progress(this);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: P3DPackage::RequiredPackage::Constructor
|
||||
// Access: Public
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
inline P3DPackage::RequiredPackage::
|
||||
RequiredPackage(const string &package_name, const string &package_version,
|
||||
P3DHost *host) :
|
||||
_package_name(package_name),
|
||||
_package_version(package_version),
|
||||
_host(host)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -169,8 +169,8 @@ get_formatted_name() const {
|
|||
////////////////////////////////////////////////////////////////////
|
||||
// Function: P3DPackage::add_instance
|
||||
// Access: Public
|
||||
// Description: Specifies an instance that may be responsible for
|
||||
// downloading this package.
|
||||
// Description: Specifies an instance that that will be using this
|
||||
// package, and may be responsible for downloading it.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void P3DPackage::
|
||||
add_instance(P3DInstance *inst) {
|
||||
|
|
@ -182,8 +182,9 @@ add_instance(P3DInstance *inst) {
|
|||
////////////////////////////////////////////////////////////////////
|
||||
// Function: P3DPackage::remove_instance
|
||||
// Access: Public
|
||||
// Description: Indicates that the given instance will no longer be
|
||||
// responsible for downloading this package.
|
||||
// Description: Indicates that the given instance is no longer
|
||||
// interested in this package and will not be
|
||||
// responsible for downloading it.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void P3DPackage::
|
||||
remove_instance(P3DInstance *inst) {
|
||||
|
|
@ -567,26 +568,33 @@ desc_file_download_finished(bool success) {
|
|||
void P3DPackage::
|
||||
got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
|
||||
TiXmlElement *xpackage = doc->FirstChildElement("package");
|
||||
TiXmlElement *xuncompressed_archive = NULL;
|
||||
TiXmlElement *xcompressed_archive = NULL;
|
||||
|
||||
if (xpackage != NULL) {
|
||||
xpackage->Attribute("patch_version", &_patch_version);
|
||||
|
||||
xuncompressed_archive = xpackage->FirstChildElement("uncompressed_archive");
|
||||
xcompressed_archive = xpackage->FirstChildElement("compressed_archive");
|
||||
|
||||
TiXmlElement *xconfig = xpackage->FirstChildElement("config");
|
||||
if (xconfig != NULL) {
|
||||
const char *display_name_cstr = xconfig->Attribute("display_name");
|
||||
if (display_name_cstr != NULL) {
|
||||
_package_display_name = display_name_cstr;
|
||||
}
|
||||
|
||||
// Save the config entry within this class for others to query.
|
||||
_xconfig = (TiXmlElement *)xconfig->Clone();
|
||||
if (xpackage == NULL) {
|
||||
nout << _package_name << " desc file contains no <package>\n";
|
||||
if (!freshly_downloaded) {
|
||||
download_desc_file();
|
||||
return;
|
||||
}
|
||||
report_done(false);
|
||||
return;
|
||||
}
|
||||
|
||||
xpackage->Attribute("patch_version", &_patch_version);
|
||||
|
||||
TiXmlElement *xconfig = xpackage->FirstChildElement("config");
|
||||
if (xconfig != NULL) {
|
||||
const char *display_name_cstr = xconfig->Attribute("display_name");
|
||||
if (display_name_cstr != NULL) {
|
||||
_package_display_name = display_name_cstr;
|
||||
}
|
||||
|
||||
// Save the config entry within this class for others to query.
|
||||
_xconfig = (TiXmlElement *)xconfig->Clone();
|
||||
}
|
||||
|
||||
TiXmlElement *xuncompressed_archive =
|
||||
xpackage->FirstChildElement("uncompressed_archive");
|
||||
TiXmlElement *xcompressed_archive =
|
||||
xpackage->FirstChildElement("compressed_archive");
|
||||
|
||||
if (xuncompressed_archive == NULL || xcompressed_archive == NULL) {
|
||||
// The desc file didn't include the archive file itself, weird.
|
||||
|
|
@ -604,18 +612,37 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
|
|||
// Now get all the extractable components.
|
||||
_unpack_size = 0;
|
||||
_extracts.clear();
|
||||
TiXmlElement *extract = xpackage->FirstChildElement("extract");
|
||||
while (extract != NULL) {
|
||||
TiXmlElement *xextract = xpackage->FirstChildElement("extract");
|
||||
while (xextract != NULL) {
|
||||
FileSpec file;
|
||||
file.load_xml(extract);
|
||||
file.load_xml(xextract);
|
||||
_extracts.push_back(file);
|
||||
_unpack_size += file.get_size();
|
||||
extract = extract->NextSiblingElement("extract");
|
||||
xextract = xextract->NextSiblingElement("extract");
|
||||
}
|
||||
|
||||
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
||||
|
||||
// Get the required packages.
|
||||
_requires.clear();
|
||||
TiXmlElement *xrequires = xpackage->FirstChildElement("requires");
|
||||
while (xrequires != NULL) {
|
||||
const char *package_name = xrequires->Attribute("name");
|
||||
const char *version = xrequires->Attribute("version");
|
||||
const char *host_url = xrequires->Attribute("host");
|
||||
if (package_name != NULL && host_url != NULL) {
|
||||
P3DHost *host = inst_mgr->get_host(host_url);
|
||||
if (version == NULL) {
|
||||
version = "";
|
||||
}
|
||||
_requires.push_back(RequiredPackage(package_name, version, host));
|
||||
}
|
||||
|
||||
xrequires = xrequires->NextSiblingElement("requires");
|
||||
}
|
||||
|
||||
// Get a list of all of the files in the directory, so we can remove
|
||||
// files that don't belong.
|
||||
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
||||
vector<string> contents;
|
||||
inst_mgr->scan_directory_recursively(_package_dir, contents);
|
||||
|
||||
|
|
|
|||
|
|
@ -211,6 +211,19 @@ private:
|
|||
|
||||
bool is_extractable(FileSpec &file, const string &filename) const;
|
||||
|
||||
public:
|
||||
class RequiredPackage {
|
||||
public:
|
||||
inline RequiredPackage(const string &package_name,
|
||||
const string &package_version,
|
||||
P3DHost *host);
|
||||
string _package_name;
|
||||
string _package_version;
|
||||
P3DHost *_host;
|
||||
};
|
||||
typedef vector<RequiredPackage> Requires;
|
||||
Requires _requires;
|
||||
|
||||
private:
|
||||
P3DHost *_host;
|
||||
int _host_contents_seq;
|
||||
|
|
|
|||
Loading…
Reference in New Issue