khanat-opennel-code/code/nel/tools/misc/xml_packer/xml_packer.cpp
2011-05-30 17:05:13 +02:00

449 lines
11 KiB
C++

// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// 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 <http://www.gnu.org/licenses/>.
#include "nel/misc/types_nl.h"
#include "nel/misc/app_context.h"
#include "nel/misc/path.h"
#include "nel/misc/sstring.h"
#include "nel/misc/algo.h"
using namespace std;
using namespace NLMISC;
enum TAction
{
pack,
unpack,
undefined
};
const string DefaultExt("xml_pack");
const uint32 MaxLineSize = 16*1024;
const string ExcludeFiles(".#*;*.log;*.bin;*.xml_pack_index");
const string ExcludeDirs("CVS");
bool isExcludedFile(const std::string &fileName)
{
static vector<string> excludeFileVect;
static bool init = false;
if (!init)
{
explode(ExcludeFiles, string(";"), excludeFileVect, true);
init = true;
}
bool excluded = false;
for (uint i=0; i<excludeFileVect.size(); ++i)
{
if (testWildCard(fileName, excludeFileVect[i]))
{
excluded = true;
break;
}
}
return excluded;
}
bool isExcludedDir(const std::string &dirName)
{
static vector<string> excludeDirVect;
static bool init = false;
if (!init)
{
explode(ExcludeDirs, string(";"), excludeDirVect, true);
}
bool excluded = false;
for (uint i=0; i<excludeDirVect.size(); ++i)
{
if (testWildCard(dirName, excludeDirVect[i]))
{
excluded = true;
break;
}
}
return excluded;
}
string getLastDirName(const std::string &path)
{
string dirName;
string::size_type pos = path.size()-1;
// skip any terminal directory separator
if (pos > 0 && (path[pos] == '\\' || path[pos] == '/'))
--pos;
while(pos > 0 && path[pos] != '\\' && path[pos] != '/' )
dirName = path[pos--] + dirName;
return dirName;
}
int main(int argc, char *argv[])
{
printf("NeL XML Packer/Unpacker V0.3\n");
CApplicationContext appContext;
TAction action = undefined;
bool recursive = false;
// compute the current folder name
string currentPath = CPath::getCurrentPath();
string dirName = getLastDirName(currentPath);;
string filename = dirName + "."+DefaultExt;
// check the params to choose action
for (uint i=0; i<uint(argc); ++i)
{
if (strcmp(argv[i], "-p") == 0)
{
// pack the current folder
action = pack;
printf("Packing files\n");
}
else if (strcmp(argv[i], "-u") == 0)
{
// unpack the current folder
action = unpack;
printf("Unpacking files\n");
}
else if (strcmp(argv[i], "-r") == 0)
{
// unpack the current folder
recursive = true;
printf("Recursive mode\n");
}
// else if (strcmp(argv[i], "-f") == 0)
// {
// if (uint(argc) < i+i)
// {
// printf("Error : missing file name after -f\n");
// return -1;
// }
// // use the specified file archive instead of the directory name
// filename = argv[++i];
// }
}
if (action == undefined)
{
printf("Usage : %s -u|-p [-r]\n", argv[0]);
printf(" -p : pack the current folder\n");
printf(" -u : unpack the current folder\n");
printf(" -r : pack or unpack subdirectories recursively\n");
return -1;
}
vector<string> dirStack;
printf("Current path is '%s'\n", CPath::getCurrentPath().c_str());
// push the current directory to start the loop
dirStack.push_back(CPath::getCurrentPath());
while(!dirStack.empty())
{
string dirName = dirStack.back();
dirStack.pop_back();
string filename = dirName+"/"+getLastDirName(dirName) + "."+DefaultExt;
switch (action)
{
case pack:
{
printf("Packing directory '%s'...\n", dirName.c_str());
// string packFileName = dirName+"/tmp."+DefaultExt;
string packFileName = filename;
string indexFileName = dirName+"/.xml_pack_index";
// get the current directory content
vector<string> files;
CPath::getPathContent(dirName, false, false, true, files);
vector<string> validFiles;
// first loop to build the list of valid file
for (uint i=0; i<files.size(); ++i)
{
if (files[i].find(DefaultExt) == files[i].size() - DefaultExt.size())
continue;
string &subFileName = files[i];
// check exclude filter
if (isExcludedFile(subFileName))
{
continue;
}
// ok, this file is valid
validFiles.push_back(subFileName);
}
bool needRepack = true;
// if an xml pack already exist in the folder...
if (CFile::fileExists(packFileName))
{
breakable
{
if (validFiles.empty())
{
// no file in the directory, erase the pack file
CFile::deleteFile(packFileName);
break;
}
uint32 packDate = CFile::getFileModificationDate(packFileName);
if (!CFile::fileExists(indexFileName) || CFile::getFileModificationDate(indexFileName) < packDate)
{
// no index file or index file older than pack file, repack
break;
}
// read the index file
set<string> fileInIndex;
char lineBuffer[1024];
FILE *fp = fopen(indexFileName.c_str(), "rt");
while (fgets(lineBuffer, 1024, fp))
fileInIndex.insert(CSString(lineBuffer).strip());
fclose(fp);
// loop to check for file time stamp
for (uint i=0; i<validFiles.size(); ++i)
{
uint32 fileDate = CFile::getFileModificationDate(validFiles[i]);
if (fileDate >= packDate)
// no more to check
break;
// remove this file from the file index
fileInIndex.erase(CFile::getFilename(validFiles[i]));
}
// check if there are some some deleted in the directory
if (!fileInIndex.empty())
{
// need to repack, there are erased files
break;
}
// all files are older than the pack file ! no repack needed
needRepack = false;
}
}
// we need to repack and have some file to store ?
if (!validFiles.empty() && needRepack)
{
// open the pack file
// FILE *fp = fopen(filename.c_str(), "wt");
FILE *fp = fopen(packFileName.c_str(), "wt");
fprintf(fp, "<nel:packed_xml>\n");
for (uint i=0; i<validFiles.size(); ++i)
{
string &subFileName = validFiles[i];
printf("Adding file '%s'...\n", CFile::getFilename(subFileName).c_str());
fprintf(fp, " <nel:xml_file name=\"%s\">\n", CFile::getFilename(subFileName).c_str());
FILE *subFp = fopen(subFileName.c_str(), "rt");
nlassert(subFp != NULL);
char buffer[MaxLineSize];
char *result;
bool needFinalReturn = false;
result = fgets(buffer, MaxLineSize, subFp);
needFinalReturn = result != NULL ? buffer[strlen(buffer)-1] != '\n' : true;
while(result != 0)
{
fputs(buffer, fp);
result = fgets(buffer, MaxLineSize, subFp);
needFinalReturn = result != NULL ? buffer[strlen(buffer)-1] != '\n' : needFinalReturn;
}
if (needFinalReturn)
{
const char *finalReturn = "\n";
fputs(finalReturn, fp);
}
fclose(subFp);
fprintf(fp, " </nel:xml_file>\n");
}
fprintf(fp, "</nel:packed_xml>\n");
fclose(fp);
// write the disposable index file used by pack to check for erased file
fp = fopen(indexFileName.c_str(), "wt");
for (uint i=0; i<validFiles.size(); ++i)
{
fprintf(fp, "%s\n", CFile::getFilename(validFiles[i]).c_str());
}
fclose(fp);
// set the file 'hidden'n use the plain old system command...
sint res = system(toString("attrib +h %s", indexFileName.c_str()).c_str());
if (res)
{
nlwarning("attrib failed with return code %d", res);
}
}
else
{
printf("Directory %s is up to date, no repack\n", dirName.c_str());
}
}
break;
case unpack:
{
printf("Unpacking directory '%s'...\n", dirName.c_str());
// open the pack file
// FILE *fp = fopen((dirName+"/tmp."+DefaultExt).c_str(), "rt");
FILE *fp = fopen(filename.c_str(), "rt");
if (!recursive)
{
// if we are not recursive, we MUST have a file here
printf("Error : can't find a xml_pack file in current directory\n");
exit(-1);
}
else
{
// just continue to recurse, there is no file at this level
break;
}
uint linecount = 0;
// read the first line
char buffer[MaxLineSize];
if (!fgets(buffer, MaxLineSize, fp) || strcmp(buffer, "<nel:packed_xml>\n") != 0)
{
printf ("Error : invalid pack file '%s'\n", filename.c_str());
return -1;
}
linecount++;
char *result = NULL;
do
{
// read a file line
linecount++;
if (!fgets(buffer, MaxLineSize, fp))
{
fclose(fp);
printf ("Error : invalid pack file '%s' at line %u", filename.c_str(), linecount);
return -1;
}
CSString parser(buffer);
if (parser.find(" <nel:xml_file name=") != 0)
{
fclose(fp);
// end of pack file
if (parser.find("</nel:packed_xml>") == 0)
break;
printf ("Error : invalid pack file '%s' at line %u", filename.c_str(), linecount);
return -1;
}
CSString subFileName = parser.leftCrop(sizeof(" <nel:xml_file name=")-1);
subFileName = subFileName.matchDelimiters(false, false, true, false);
subFileName = subFileName.unquoteIfQuoted();
subFileName = dirName+"/"+subFileName;
printf("Extracting file '%s'...\n", CFile::getFilename(subFileName).c_str());
// open the output file
FILE *output = fopen (subFileName.c_str(), "wt");
if (output == NULL)
{
printf ("Error : can not open output file '%s' from pack file '%s'", subFileName.c_str(), filename.c_str());
exit(-1);
}
result = fgets(buffer, MaxLineSize, fp);
linecount++;
while (result != NULL && strcmp(buffer, " </nel:xml_file>\n") != 0)
{
fputs(result, output);
// read next line
result = fgets(buffer, MaxLineSize, fp);
linecount++;
}
fclose(output);
} while(result != NULL);
}
break;
default:
// this shouldn't happen / keep compiler happy
break;
}
if (recursive)
{
vector<string> subDirs;
CPath::getPathContent(dirName, false, true, false, subDirs);
// filter the directories
for (uint i=(uint)subDirs.size(); i>0; --i)
{
if (!isExcludedDir(subDirs[i-1]))
dirStack.push_back(subDirs[i-1]);
}
}
}
return 0;
}