// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . //----------------------------------------------------------------------------- // includes //----------------------------------------------------------------------------- #include "stdpch.h" #include "nel/misc/path.h" #include "nel/misc/sha1.h" #include "bnp_patch.h" #define PERSISTENT_TOKEN_FAMILY RyzomTokenFamily //----------------------------------------------------------------------------- // Handy utility routines //----------------------------------------------------------------------------- void normaliseBnpFileName(std::string& fileName) { BOMB_IF(fileName.empty(),"Can't normalise an empty bnp file name",return); if (NLMISC::CFile::getExtension(fileName).empty() && fileName[fileName.size()-1]!='.') fileName+=".bnp"; } void applyDate (const std::string &sFilename, uint32 nDate) { // change the file time if(nDate != 0) { // _utimbuf utb; // utb.actime = utb.modtime = nDate; NLMISC::CFile::setRWAccess(sFilename); NLMISC::CFile::setFileModificationDate(sFilename, nDate); // _utime (sFilename.c_str (), &utb); } } //----------------------------------------------------------------------------- // class CBNPFileVersion //----------------------------------------------------------------------------- CBNPFileVersion::CBNPFileVersion() { _FileTime= 0; _FileSize= 0; _7ZFileSize=0; _PatchSize= 0; _VersionNumber= std::numeric_limits::max(); } // setup record contents from a file name and version number // returns false if the file didn't exist bool CBNPFileVersion::setup(const std::string &fileName, uint32 versionNumber) { // make sure the file exists... BOMB_IF(!NLMISC::CFile::fileExists(fileName),("File not found: "+fileName).c_str(),return false); // generate a hash key for the file and store it in a vector of uint32 CHashKey hashKey= getSHA1(fileName); nlassert(hashKey.HashKeyString.size()==20); _HashKey.clear(); for (uint32 i=0;i<5;++i) _HashKey.push_back(*(uint32*)&hashKey.HashKeyString[4*i]); // get the other file properties _FileTime= NLMISC::CFile::getFileModificationDate(fileName); _FileSize= NLMISC::CFile::getFileSize(fileName); // setup the version number _VersionNumber= versionNumber; return true; } void CBNPFileVersion::setVersionNumber(uint32 nVersionNumber) { _VersionNumber = nVersionNumber; } void CBNPFileVersion::set7ZipFileSize(uint32 n7ZFileSize) { _7ZFileSize = n7ZFileSize; } void CBNPFileVersion::setPatchSize(uint32 nPatchSize) { _PatchSize = nPatchSize; } void CBNPFileVersion::setTimeStamp(uint32 nTimeStamp) { _FileTime = nTimeStamp; } // accessors uint32 CBNPFileVersion::getVersionNumber() const { return _VersionNumber; } uint32 CBNPFileVersion::getTimeStamp() const { return _FileTime; } uint32 CBNPFileVersion::getFileSize() const { return _FileSize; } uint32 CBNPFileVersion::get7ZFileSize() const { return _7ZFileSize; } uint32 CBNPFileVersion::getPatchSize() const { return _PatchSize; } CHashKey CBNPFileVersion::getHashKey() const { nlassert(_HashKey.size()==5); CHashKey hashKey; for (uint32 i=0;i<5;++i) { *(uint32*)&hashKey.HashKeyString[4*i]=_HashKey[i]; } return hashKey; } // == operator bool CBNPFileVersion::operator==(const CBNPFileVersion& other) const { // make sure the file sizes match if (_FileSize!=other._FileSize) return false; // make sure the hash keys match if (_HashKey!=other._HashKey) return false; // we don't compare version numbers or file dates as they're not interesting return true; } // != operator bool CBNPFileVersion::operator!=(const CBNPFileVersion& other) const { return !operator==(other); } //----------------------------------------------------------------------------- // Persistent data for CBNPFileVersion //----------------------------------------------------------------------------- #define PERSISTENT_CLASS CBNPFileVersion #define PERSISTENT_DATA \ PROP(uint32,_VersionNumber) \ PROP(uint32,_FileSize) \ PROP(uint32,_7ZFileSize) \ PROP(uint32,_FileTime) \ PROP(uint32,_PatchSize) \ PROP_VECT(uint32,_HashKey) //# pragma message( PERSISTENT_GENERATION_MESSAGE ) #include "persistent_data_template.h" #undef PERSISTENT_CLASS #undef PERSISTENT_DATA //----------------------------------------------------------------------------- // class CBNPFile //----------------------------------------------------------------------------- CBNPFile::CBNPFile() { _IsIncremental= true; } bool CBNPFile::addVersion(const std::string& bnpDirectory, const std::string& /* refDirectory */, IVersionNumberGenerator& version) { nlinfo("Checking need to add new version to file: %s",_FileName.c_str()); // perform a quick check to see if the time stamp and file size of the new BNP file match the last version in the index std::string fullFileName= bnpDirectory+_FileName; if (!NLMISC::CFile::fileExists(fullFileName)) return false; if (!_Versions.empty()) { if ((NLMISC::CFile::getFileSize(fullFileName)==(uint32)_Versions.back().getFileSize()) && (NLMISC::CFile::getFileModificationDate(fullFileName)==(uint32)_Versions.back().getTimeStamp())) return true; NLMISC::InfoLog->displayNL("File: %s\n size(%d != %d) || time(%d != %d)", fullFileName.c_str(), NLMISC::CFile::getFileSize(fullFileName), (uint32)_Versions.back().getFileSize(), NLMISC::CFile::getFileModificationDate(fullFileName), (uint32)_Versions.back().getTimeStamp() ); } // create a new record for the BNP file that we have on the disk at the moment // if no file was found then give up (return) CBNPFileVersion fileVersion; bool result= fileVersion.setup(fullFileName,~0u); if (result==false) return false; // compare the fileVersion record to the last record in the history. // If they don't match then append it if (_Versions.empty() || _Versions.back()!=fileVersion) { // if we haven't yet generated the version number for this version then go for it now version.grabVersionNumber(); fileVersion.setVersionNumber(version.getPackageVersionNumber()); // make sure that our version numbers are ever increasing... it would be fatal to have an out-of-order version if (!_Versions.empty()) nlassert(_Versions.back().getVersionNumber()1) { _Versions[0]= _Versions.back(); _Versions.resize(1); } return true; } uint32 CBNPFile::getLatestVersionNumber(uint32 max) const { if (_Versions.empty()) return 0; uint32 i=0; for (i=(uint32)_Versions.size();i--;) if (_Versions[i].getVersionNumber()<=max) return _Versions[i].getVersionNumber(); nlinfo("File %s didn't exist before version %d",_FileName.c_str(),max); return 0; } uint32 CBNPFile::versionCount() const { return (uint32)_Versions.size(); } const CBNPFileVersion& CBNPFile::getVersion(uint32 idx) const { nlassert(idx1) { _Versions[0]= _Versions.back(); _Versions.resize(1); } } bool CBNPFile::isIncremental() { return _IsIncremental; } //----------------------------------------------------------------------------- // Persistent data for CBNPFile //----------------------------------------------------------------------------- #define PERSISTENT_CLASS CBNPFile #define PERSISTENT_DATA\ PROP(std::string,_FileName)\ STRUCT_VECT(_Versions) //# pragma message( PERSISTENT_GENERATION_MESSAGE ) #include "persistent_data_template.h" #undef PERSISTENT_CLASS #undef PERSISTENT_DATA //----------------------------------------------------------------------------- // class CBNPFileSet //----------------------------------------------------------------------------- void CBNPFileSet::removeFile(const std::string &filename) { for( uint k = 0; k < _Files.size(); ++k) { if (_Files[k].getFileName() == filename) { _Files.erase(_Files.begin() + k); } } } // add a version to the file // returns highest version number in files after operation uint32 CBNPFileSet::addVersion(const std::string& bnpDirectory, const std::string& refDirectory, IVersionNumberGenerator& version) { nlinfo("Updating package index..."); uint32 result=0; // add versions to different files for (uint32 i=(uint32)_Files.size();i--;) if (_Files[i].addVersion(bnpDirectory,refDirectory,version)!=false) result= std::max(result,_Files[i].getLatestVersionNumber()); return result; } // look through the referenced files for the highest version number uint32 CBNPFileSet::getVersionNumber() const { uint32 result=0; for (uint32 i=(uint32)_Files.size();i--;) result= std::max(result,_Files[i].getLatestVersionNumber()); return result; } void CBNPFileSet::clear() { _Files.clear(); } uint32 CBNPFileSet::fileCount() const { return (uint32)_Files.size(); } const CBNPFile& CBNPFileSet::getFile(uint32 idx) const { return const_cast(this)->getFile(idx); } const CBNPFile* CBNPFileSet::getFileByName(const std::string& fileName) const { return const_cast(this)->getFileByName(fileName); } CBNPFile& CBNPFileSet::getFile(uint32 idx) { nlassert(idxsetIncremental(false); return; } // file is new so need to add it std::string s= fileName; normaliseBnpFileName(s); nlinfo("- adding file: %s",s.c_str()); _Files.resize(_Files.size()+1); _Files.back().setFileName(s); _Files.back().setIncremental(isIncremental); } //----------------------------------------------------------------------------- // Persistent data for CBNPFileSet //----------------------------------------------------------------------------- #define PERSISTENT_CLASS CBNPFileSet #define PERSISTENT_DATA\ STRUCT_VECT(_Files) //# pragma message( PERSISTENT_GENERATION_MESSAGE ) #include "persistent_data_template.h" #undef PERSISTENT_CLASS #undef PERSISTENT_DATA //----------------------------------------------------------------------------- // class CBNPCategory //----------------------------------------------------------------------------- CBNPCategory::CBNPCategory() { _IsOptional=true; _IsIncremental=true; _Hidden=false; } bool CBNPCategory::hasFile(const std::string &fileName) const { return std::find(_Files.begin(), _Files.end(), fileName) != _Files.end(); } const std::string& CBNPCategory::getName() const { return _Name; } void CBNPCategory::setName(const std::string& name) { _Name=name; } void CBNPCategory::setOptional(bool value) { _IsOptional= value; } bool CBNPCategory::isOptional() const { return _IsOptional; } void CBNPCategory::setUnpackTo(const std::string &n) { _UnpackTo = n; } const std::string &CBNPCategory::getUnpackTo() const { return _UnpackTo; } void CBNPCategory::setIncremental(bool value) { _IsIncremental= value; } bool CBNPCategory::isIncremental() const { return _IsIncremental; } void CBNPCategory::setCatRequired(const std::string &cat) { _CatRequired = cat; } const std::string &CBNPCategory::getCatRequired() const { return _CatRequired; } void CBNPCategory::setHidden(bool value) { _Hidden = value; } bool CBNPCategory::isHidden() const { return _Hidden; } uint32 CBNPCategory::fileCount() const { return (uint32)_Files.size(); } const std::string& CBNPCategory::getFile(uint32 idx) const { nlassert(idx::const_iterator it = _Category.begin(); it != _Category.end(); ++it) { if (it->hasFile(fileName)) { return &(*it); } } return NULL; } void CBNPCategorySet::deleteCategory(uint32 index) { nlassert(index < _Category.size()); _Category.erase(_Category.begin() + index); } uint32 CBNPCategorySet::fileCount() const { uint32 result=0; for (uint32 i=0;i<_Category.size();++i) result+=_Category[i].fileCount(); return result; } const std::string& CBNPCategorySet::getFile(uint32 idx) const { uint32 i=0; for (;;++i) { nlassert(i<_Category.size()); if (_Category[i].fileCount()>idx) break; idx-=_Category[i].fileCount(); } return _Category[i].getFile(idx); } uint32 CBNPCategorySet::categoryCount() const { return (uint32)_Category.size(); } CBNPCategory& CBNPCategorySet::getCategory(uint32 idx) { nlassert(idx(this)->getCategory(idx); } const CBNPCategory* CBNPCategorySet::getCategory(const std::string& categoryName) const { // look for a category with matching name for (uint32 i=0;ifileCount();++i) if (theCategory->getFile(i)==fileName) return; // the file doesn't already exist so add it theCategory->addFile(fileName); nlinfo("- File added to category %s::%s",categoryName.c_str(),fileName.c_str()); } bool CBNPCategorySet::isFileIncremental(const std::string& fileName) const { // for each category for (uint32 i=0;iisOptional() && // _SelectedCategories.find(theCategory->getName())==_SelectedCategories.end())) // continue; // } // else // { // if (theCategory->isOptional()!=isOptionalFlag) // continue; // } // // // if one of the files is not up to date then we return 'true' as we need to patch // for (uint32 j=0;jfileCount();++j) // if (!isFileUpToDate(theCategory->getFile(j))) // return true; // } // // return false; //} // //bool CBNPUnpatcher::isPatchMandatory() //{ // return _isPatch(false,false); //} // //bool CBNPUnpatcher::isPatchOptional() //{ // return _isPatch(false,true); //} // //bool CBNPUnpatcher::isPatchRequired() //{ // return _isPatch(true); //} // //// return true if it's necessary to download patches for the selected options //bool CBNPUnpatcher::isDownloadRequired() //{ // if (!isPatchRequired()) // return false; // // std::vector hold; // getDownloadPatches(hold); // return !hold.empty(); //} // //// return true if it's necessary to download the next patch that needs to be applied (in order) //bool CBNPUnpatcher::isNextPatchDownloadRequired() //{ // if (!isPatchRequired()) // return false; // // return getNextPatch().getRequiresDownload(); //} // // ////----------------------------------------------------------------------------- //// crunching routines // //// scan the directories for files - identifies the set of required patches //// and also the set of these patches that is missing from the patch directory //void CBNPUnpatcher::scanForFiles() //{ // std::vector patchFiles; // std::vector patchFiles; // std::vector dataFiles; // // // get the list of files in the patch directory // NLMISC::CPath::getPathContent(getPatchDirectory(),false,false,true,patchFiles); // // // get the list of files in the data directory // NLMISC::CPath::getPathContent(getDataDirectory(),false,false,true,dataFiles); // // for (uint32 i=0;i<_Categories.fileCount();++i) // { // _Categories.getFile() // result.push_back(_Categories.getCategory(i).getName()); // } // xxx //} // //// apply the mandatory and selected optional patches //// nlerror if isDownloadRequired() is not false //void CBNPUnpatcher::applyPatches() //{ // nlassert(isIndexUpToDate()==true); // nlassert(!isDownloadRequired()); // xxx //} // //// apply the next patch (in order) //// nlerror if isNextPatchDownloadRequired() is not false //void CBNPUnpatcher::applyNextPatch() //{ // // note that if the index isn't up to date then it is classed as the next patch // nlassert(!isNextPatchDownloadRequired()); // xxx //} // // ////----------------------------------------------------------------------------- //// managing the set of selected optional patch categories // //// get the names of all optional categories //void CBNPUnpatcher::getAllOptionalCategories(std::vector& result) //{ // nlassert(isIndexUpToDate()==true); // // result.clear(); // for (uint32 i=0;i<_Categories.categoryCount();++i) // { // result.push_back(_Categories.getCategory(i).getName()); // } //} // //// get the names of the optional categories that require patching //void CBNPUnpatcher::getPatchableOptionalCategories(std::vector& result) //{ // nlassert(isIndexUpToDate()==true); // // result.clear(); // for (uint32 i=0;i<_Categories.categoryCount();++i) // { // CBNPCategory* theCategory= _Categories.getCategory(i); // uint32 j; // // // if one of the files is not up to date then we return 'true' as we need to patch // for (j=0;jfileCount();++j) // if (!isFileUpToDate(theCategory->getFile(j))) // break; // // // if we broke out before the end of the for loop then we need to add this category // if (jfileCount()) // result.push_back(_Categories.getCategory(i).getName()); // } //} // //// select or unselect an optional package //void CBNPUnpatcher::setOptionalCategorySelectFlag(const std::string& categoryName, bool value) //{ // nlassert(isIndexUpToDate()==true); // _SelectedCategories.insert(categoryName); //} // //// select or unselect all optional packages //void CBNPUnpatcher::setAllOptionalCategorySelectFlags(bool value) //{ // nlassert(isIndexUpToDate()==true); // // _SelectedCategories.clear(); // for (uint32 i=0;i<_Categories.categoryCount();++i) // { // _SelectedCategories.insert(_Categories.getCategory(i).getName()); // } //} // // ////----------------------------------------------------------------------------- //// getting lists of applicable patches // //// get the ordered list of mandatory + optional patches that need to be applied to update selected packages //void CBNPUnpatcher::getSelectedPatches(std::vector& result) //{ // nlassert(isIndexUpToDate()==true); // // std::vector mandatoryPatches; // getMandatoryPatches(mandatoryPatches); // // std::vector optionalPatches; // getSelectedOptionalPatches(optionalPatches); // // result= mandatoryPatches+ optionalPatches; //} // //// get the ordered list of optional patches that need to be applied to update selected packages //void CBNPUnpatcher::getSelectedOptionalPatches(std::vector& result) //{ // nlassert(isIndexUpToDate()==true); // xxx //} // //// get the ordered list of patches that need to be applied for a minimum update //void CBNPUnpatcher::getMandatoryPatches(std::vector& result) //{ // nlassert(isIndexUpToDate()==true); // xxx //} // //// get an ordered list of the patches that need to be applied for a full update //void CBNPUnpatcher::getAllPatches(std::vector& result) //{ // // store the selected category set in temporary variable // std::set selectedCategories= _SelectedCategories; // // // select all of the categories and delegate to getSelectedPatches() // setAllOptionalCategorySelectFlags(); // getSelectedPatches(result); // // // restore the _SelectedCategories set from temp variable // _SelectedCategories= selectedCategories; //} // //// get the name of the next patch that needs to be applied (for progress display) //const std::string& CBNPUnpatcher::getNextPatchName() //{ // CBNPPatchDescription patch= getNextPatch(); // return patch.getTargetFileName()+NLMISC::toString(":%d",patch.getVersion()); //} // //// get the patch description for the next patch to apply //CBNPPatchDescription CBNPUnpatcher::getNextPatch() //{ // // make sure that index is up to date and patching is required // nlassert(isIndexUpToDate()); // nlassert(isPatchRequired()); // // // treat the case of !uptodate() here and get the index file as the next patch // if (!isIndexUpToDate()) // { // return getIndexFileDownloadDescription(); // } // // for (uint32 i=0;i<_Categories.categoryCount();++i) // { // CBNPCategory* theCategory= _Categories.getCategory(i); // uint32 j; // // // if one of the files is not up to date then we return 'true' as we need to patch // for (j=0;jfileCount();++j) // if (!isFileUpToDate(theCategory->getFile(j))) // break; // // // if we broke out before the end of the for loop then we need to add this category // if (jfileCount()) // result.push_back(_Categories.getCategory(i).getName()); // } // // xxx //} // //// get the list of patches that need to be downloaded //void CBNPUnpatcher::getSelectedDownloadPatches(std::vector& result) //{ // nlassert(isIndexUpToDate()==true); // xxx //} // //// get the list of patches that need to be downloaded //void CBNPUnpatcher::getAllDownloadPatches(std::vector& result) //{ // // store the selected category set in temporary variable // std::set selectedCategories= _SelectedCategories; // // // select all of the categories and delegate to getSelectedDownloadPatches() // setAllOptionalCategorySelectFlags(); // getSelectedDownloadPatches(result); // // // restore the _SelectedCategories set from temp variable // _SelectedCategories= selectedCategories; //} // // ////----------------------------------------------------------------------------- // // //#endif