1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2015 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "nel/misc/types_nl.h"
21 #include "nel/misc/app_context.h"
22 #include "nel/misc/path.h"
23 #include "nel/misc/common.h"
24 #include "nel/misc/sstring.h"
25 #include "nel/misc/algo.h"
28 using namespace NLMISC
;
37 const string
DefaultExt("xml_pack");
38 const uint32 MaxLineSize
= 16*1024;
40 const string
ExcludeFiles(".#*;*.log;*.bin;*.xml_pack_index");
41 const string
ExcludeDirs("CVS");
43 bool isExcludedFile(const std::string
&fileName
)
45 static vector
<string
> excludeFileVect
;
46 static bool init
= false;
50 explode(ExcludeFiles
, string(";"), excludeFileVect
, true);
54 bool excluded
= false;
56 for (uint i
=0; i
<excludeFileVect
.size(); ++i
)
58 if (testWildCard(fileName
, excludeFileVect
[i
]))
68 bool isExcludedDir(const std::string
&dirName
)
70 static vector
<string
> excludeDirVect
;
71 static bool init
= false;
75 explode(ExcludeDirs
, string(";"), excludeDirVect
, true);
78 bool excluded
= false;
80 for (uint i
=0; i
<excludeDirVect
.size(); ++i
)
82 if (testWildCard(dirName
, excludeDirVect
[i
]))
92 string
getLastDirName(const std::string
&path
)
95 string::size_type pos
= path
.size()-1;
97 // skip any terminal directory separator
98 if (pos
> 0 && (path
[pos
] == '\\' || path
[pos
] == '/'))
101 while(pos
> 0 && path
[pos
] != '\\' && path
[pos
] != '/' )
102 dirName
= path
[pos
--] + dirName
;
107 int main(int argc
, char *argv
[])
109 printf("NeL XML Packer/Unpacker V0.3\n");
110 CApplicationContext appContext
;
112 TAction action
= undefined
;
113 bool recursive
= false;
115 // compute the current folder name
116 string currentPath
= CPath::getCurrentPath();
117 string dirName
= getLastDirName(currentPath
);;
119 string filename
= dirName
+ "."+DefaultExt
;
121 // check the params to choose action
122 for (uint i
=0; i
<uint(argc
); ++i
)
124 if (strcmp(argv
[i
], "-p") == 0)
126 // pack the current folder
128 printf("Packing files\n");
130 else if (strcmp(argv
[i
], "-u") == 0)
132 // unpack the current folder
134 printf("Unpacking files\n");
136 else if (strcmp(argv
[i
], "-r") == 0)
138 // unpack the current folder
140 printf("Recursive mode\n");
142 // else if (strcmp(argv[i], "-f") == 0)
144 // if (uint(argc) < i+i)
146 // printf("Error : missing file name after -f\n");
149 // // use the specified file archive instead of the directory name
150 // filename = argv[++i];
154 if (action
== undefined
)
156 printf("Usage : %s -u|-p [-r]\n", argv
[0]);
157 printf(" -p : pack the current folder\n");
158 printf(" -u : unpack the current folder\n");
159 printf(" -r : pack or unpack subdirectories recursively\n");
164 vector
<string
> dirStack
;
166 printf("Current path is '%s'\n", CPath::getCurrentPath().c_str());
168 // push the current directory to start the loop
169 dirStack
.push_back(CPath::getCurrentPath());
172 while(!dirStack
.empty())
174 string dirName
= dirStack
.back();
176 string filename
= dirName
+"/"+getLastDirName(dirName
) + "."+DefaultExt
;
181 printf("Packing directory '%s'...\n", dirName
.c_str());
182 // string packFileName = dirName+"/tmp."+DefaultExt;
183 string packFileName
= filename
;
184 string indexFileName
= dirName
+"/.xml_pack_index";
186 // get the current directory content
187 vector
<string
> files
;
188 CPath::getPathContent(dirName
, false, false, true, files
);
190 vector
<string
> validFiles
;
192 // first loop to build the list of valid file
193 for (uint i
=0; i
<files
.size(); ++i
)
195 if (files
[i
].find(DefaultExt
) == files
[i
].size() - DefaultExt
.size())
198 string
&subFileName
= files
[i
];
200 // check exclude filter
201 if (isExcludedFile(subFileName
))
206 // ok, this file is valid
207 validFiles
.push_back(subFileName
);
210 bool needRepack
= true;
212 // if an xml pack already exist in the folder...
213 if (CFile::fileExists(packFileName
))
217 if (validFiles
.empty())
219 // no file in the directory, erase the pack file
220 CFile::deleteFile(packFileName
);
224 uint32 packDate
= CFile::getFileModificationDate(packFileName
);
226 if (!CFile::fileExists(indexFileName
) || CFile::getFileModificationDate(indexFileName
) < packDate
)
228 // no index file or index file older than pack file, repack
232 // read the index file
233 set
<string
> fileInIndex
;
234 char lineBuffer
[1024];
235 FILE *fp
= nlfopen(indexFileName
, "rt");
236 while (fgets(lineBuffer
, 1024, fp
))
237 fileInIndex
.insert(CSString(lineBuffer
).strip());
241 // loop to check for file time stamp
242 for (uint i
=0; i
<validFiles
.size(); ++i
)
244 uint32 fileDate
= CFile::getFileModificationDate(validFiles
[i
]);
246 if (fileDate
>= packDate
)
250 // remove this file from the file index
251 fileInIndex
.erase(CFile::getFilename(validFiles
[i
]));
254 // check if there are some some deleted in the directory
255 if (!fileInIndex
.empty())
257 // need to repack, there are erased files
261 // all files are older than the pack file ! no repack needed
266 // we need to repack and have some file to store ?
267 if (!validFiles
.empty() && needRepack
)
269 // open the pack file
270 // FILE *fp = nlfopen(filename, "wt");
271 FILE *fp
= nlfopen(packFileName
, "wt");
273 fprintf(fp
, "<nel:packed_xml>\n");
275 for (uint i
=0; i
<validFiles
.size(); ++i
)
277 string
&subFileName
= validFiles
[i
];
279 printf("Adding file '%s'...\n", CFile::getFilename(subFileName
).c_str());
280 fprintf(fp
, " <nel:xml_file name=\"%s\">\n", CFile::getFilename(subFileName
).c_str());
282 FILE *subFp
= nlfopen(subFileName
, "rt");
283 nlassert(subFp
!= NULL
);
284 char buffer
[MaxLineSize
];
286 bool needFinalReturn
= false;
287 result
= fgets(buffer
, MaxLineSize
, subFp
);
288 needFinalReturn
= result
!= NULL
? buffer
[strlen(buffer
)-1] != '\n' : true;
292 result
= fgets(buffer
, MaxLineSize
, subFp
);
293 needFinalReturn
= result
!= NULL
? buffer
[strlen(buffer
)-1] != '\n' : needFinalReturn
;
297 const char *finalReturn
= "\n";
298 fputs(finalReturn
, fp
);
303 fprintf(fp
, " </nel:xml_file>\n");
306 fprintf(fp
, "</nel:packed_xml>\n");
310 // write the disposable index file used by pack to check for erased file
311 fp
= nlfopen(indexFileName
, "wt");
312 for (uint i
=0; i
<validFiles
.size(); ++i
)
314 fprintf(fp
, "%s\n", CFile::getFilename(validFiles
[i
]).c_str());
317 // set the file 'hidden'n use the plain old system command...
318 sint res
= system(toString("attrib +h %s", indexFileName
.c_str()).c_str());
321 nlwarning("attrib failed with return code %d", res
);
326 printf("Directory %s is up to date, no repack\n", dirName
.c_str());
332 printf("Unpacking directory '%s'...\n", dirName
.c_str());
333 // open the pack file
334 // FILE *fp = nlfopen(dirName+"/tmp."+DefaultExt, "rt");
335 FILE *fp
= nlfopen(filename
, "rt");
338 // if we are not recursive, we MUST have a file here
339 printf("Error : can't find a xml_pack file in current directory\n");
344 // just continue to recurse, there is no file at this level
349 // read the first line
350 char buffer
[MaxLineSize
];
351 if (!fgets(buffer
, MaxLineSize
, fp
) || strcmp(buffer
, "<nel:packed_xml>\n") != 0)
353 printf ("Error : invalid pack file '%s'\n", filename
.c_str());
364 if (!fgets(buffer
, MaxLineSize
, fp
))
367 printf ("Error : invalid pack file '%s' at line %u", filename
.c_str(), linecount
);
370 CSString
parser(buffer
);
371 if (parser
.find(" <nel:xml_file name=") != 0)
376 if (parser
.find("</nel:packed_xml>") == 0)
379 printf ("Error : invalid pack file '%s' at line %u", filename
.c_str(), linecount
);
383 CSString subFileName
= parser
.leftCrop(sizeof(" <nel:xml_file name=")-1);
384 subFileName
= subFileName
.matchDelimiters(false, false, true, false);
385 subFileName
= subFileName
.unquoteIfQuoted();
386 subFileName
= dirName
+ "/" + subFileName
.c_str();
388 printf("Extracting file '%s'...\n", CFile::getFilename(subFileName
).c_str());
389 // open the output file
390 FILE *output
= fopen (subFileName
.c_str(), "wt");
393 printf ("Error : can not open output file '%s' from pack file '%s'", subFileName
.c_str(), filename
.c_str());
397 result
= fgets(buffer
, MaxLineSize
, fp
);
399 while (result
!= NULL
&& strcmp(buffer
, " </nel:xml_file>\n") != 0)
401 fputs(result
, output
);
403 result
= fgets(buffer
, MaxLineSize
, fp
);
409 } while(result
!= NULL
);
414 // this shouldn't happen / keep compiler happy
420 vector
<string
> subDirs
;
421 CPath::getPathContent(dirName
, false, true, false, subDirs
);
423 // filter the directories
424 for (uint i
=(uint
)subDirs
.size(); i
>0; --i
)
426 if (!isExcludedDir(subDirs
[i
-1]))
427 dirStack
.push_back(subDirs
[i
-1]);