Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL Utilities
4 : * Purpose: Command line application to build seek-optimized ZIP files
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2022, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * Permission is hereby granted, free of charge, to any person obtaining a
11 : * copy of this software and associated documentation files (the "Software"),
12 : * to deal in the Software without restriction, including without limitation
13 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 : * and/or sell copies of the Software, and to permit persons to whom the
15 : * Software is furnished to do so, subject to the following conditions:
16 : *
17 : * The above copyright notice and this permission notice shall be included
18 : * in all copies or substantial portions of the Software.
19 : *
20 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 : * DEALINGS IN THE SOFTWARE.
27 : ****************************************************************************/
28 :
29 : #include "cpl_string.h"
30 : #include "cpl_time.h"
31 : #include "cpl_progress.h"
32 : #include "gdal_version.h"
33 : #include "gdal_priv.h"
34 : #include "commonutils.h"
35 : #include "gdalargumentparser.h"
36 :
37 : #include <limits>
38 :
39 : /************************************************************************/
40 : /* Validate() */
41 : /************************************************************************/
42 :
43 2 : static int Validate(const char *pszZipFilename, bool bVerbose)
44 : {
45 2 : VSIDIR *psDir = VSIOpenDir(
46 4 : (std::string("/vsizip/") + pszZipFilename).c_str(), -1, nullptr);
47 2 : if (!psDir)
48 : {
49 0 : fprintf(stderr, "%s is not a valid .zip file\n", pszZipFilename);
50 0 : return 1;
51 : }
52 :
53 2 : int nCountInvalidSOZIP = 0;
54 2 : int nCountValidSOZIP = 0;
55 2 : int ret = 0;
56 50 : while (auto psEntry = VSIGetNextDirEntry(psDir))
57 : {
58 48 : if (!VSI_ISDIR(psEntry->nMode))
59 : {
60 94 : const std::string osFilenameInZip = std::string("/vsizip/{") +
61 94 : pszZipFilename + "}/" +
62 94 : psEntry->pszName;
63 47 : if (bVerbose)
64 0 : printf("Testing %s...\n", psEntry->pszName);
65 :
66 : char **papszMD =
67 47 : VSIGetFileMetadata(osFilenameInZip.c_str(), "ZIP", nullptr);
68 : bool bSeekOptimizedFound =
69 47 : CSLFetchNameValue(papszMD, "SOZIP_FOUND") != nullptr;
70 : bool bSeekOptimizedValid =
71 47 : CSLFetchNameValue(papszMD, "SOZIP_VALID") != nullptr;
72 : const char *pszChunkSize =
73 47 : CSLFetchNameValue(papszMD, "SOZIP_CHUNK_SIZE");
74 47 : if (bSeekOptimizedValid)
75 : {
76 3 : if (bVerbose)
77 0 : printf(" %s has an associated .sozip.idx file\n",
78 0 : psEntry->pszName);
79 :
80 : const char *pszStartIdxDataOffset =
81 3 : CSLFetchNameValue(papszMD, "SOZIP_START_DATA_OFFSET");
82 : const vsi_l_offset nStartIdxOffset =
83 3 : std::strtoull(pszStartIdxDataOffset, nullptr, 10);
84 3 : VSILFILE *fpRaw = VSIFOpenL(pszZipFilename, "rb");
85 3 : CPLAssert(fpRaw);
86 :
87 3 : if (VSIFSeekL(fpRaw, nStartIdxOffset + 4, SEEK_SET) != 0)
88 : {
89 0 : fprintf(stderr, "VSIFSeekL() failed.\n");
90 0 : ret = 1;
91 : }
92 3 : uint32_t nToSkip = 0;
93 3 : if (VSIFReadL(&nToSkip, sizeof(nToSkip), 1, fpRaw) != 1)
94 : {
95 0 : fprintf(stderr, "VSIFReadL() failed.\n");
96 0 : ret = 1;
97 : }
98 3 : CPL_LSBPTR32(&nToSkip);
99 :
100 3 : if (VSIFSeekL(fpRaw, nStartIdxOffset + 32 + nToSkip,
101 3 : SEEK_SET) != 0)
102 : {
103 0 : fprintf(stderr, "VSIFSeekL() failed.\n");
104 0 : ret = 1;
105 : }
106 3 : const int nChunkSize = atoi(pszChunkSize);
107 3 : const uint64_t nCompressedSize = std::strtoull(
108 : CSLFetchNameValue(papszMD, "COMPRESSED_SIZE"), nullptr, 10);
109 3 : const uint64_t nUncompressedSize = std::strtoull(
110 : CSLFetchNameValue(papszMD, "UNCOMPRESSED_SIZE"), nullptr,
111 : 10);
112 6 : if (nChunkSize == 0 || // cannot happen
113 3 : (nUncompressedSize - 1) / nChunkSize >
114 3 : static_cast<uint64_t>(std::numeric_limits<int>::max()))
115 : {
116 0 : fprintf(
117 : stderr,
118 : "* File %s has a SOZip index, but (nUncompressedSize - "
119 : "1) / nChunkSize > INT_MAX !\n",
120 0 : psEntry->pszName);
121 0 : nCountInvalidSOZIP++;
122 0 : ret = 1;
123 0 : CSLDestroy(papszMD);
124 0 : continue;
125 : }
126 3 : int nChunksItems =
127 3 : static_cast<int>((nUncompressedSize - 1) / nChunkSize);
128 :
129 3 : if (bVerbose)
130 0 : printf(" %s: checking index offset values...\n",
131 0 : psEntry->pszName);
132 :
133 6 : std::vector<uint64_t> anOffsets;
134 : try
135 : {
136 3 : anOffsets.reserve(nChunksItems);
137 : }
138 0 : catch (const std::exception &)
139 : {
140 0 : nChunksItems = 0;
141 0 : fprintf(stderr,
142 : "Cannot allocate memory for chunk offsets.\n");
143 0 : ret = 1;
144 : }
145 :
146 109 : for (int i = 0; i < nChunksItems; ++i)
147 : {
148 106 : uint64_t nOffset64 = 0;
149 106 : if (VSIFReadL(&nOffset64, sizeof(nOffset64), 1, fpRaw) != 1)
150 : {
151 0 : fprintf(stderr, "VSIFReadL() failed.\n");
152 0 : ret = 1;
153 : }
154 106 : CPL_LSBPTR64(&nOffset64);
155 106 : if (nOffset64 >= nCompressedSize)
156 : {
157 0 : bSeekOptimizedValid = false;
158 0 : fprintf(stderr,
159 : "Error: file %s, offset[%d] (= " CPL_FRMT_GUIB
160 : ") >= compressed_size is invalid.\n",
161 0 : psEntry->pszName, i,
162 : static_cast<GUIntBig>(nOffset64));
163 : }
164 106 : if (!anOffsets.empty())
165 : {
166 103 : const auto nPrevOffset = anOffsets.back();
167 103 : if (nOffset64 <= nPrevOffset)
168 : {
169 0 : bSeekOptimizedValid = false;
170 0 : fprintf(
171 : stderr,
172 : "Error: file %s, offset[%d] (= " CPL_FRMT_GUIB
173 : ") <= offset[%d] (= " CPL_FRMT_GUIB ")\n",
174 0 : psEntry->pszName, i + 1,
175 : static_cast<GUIntBig>(nOffset64), i,
176 : static_cast<GUIntBig>(nPrevOffset));
177 : }
178 : }
179 3 : else if (nOffset64 < 9)
180 : {
181 0 : bSeekOptimizedValid = false;
182 0 : fprintf(stderr,
183 : "Error: file %s, offset[0] (= " CPL_FRMT_GUIB
184 : ") is invalid.\n",
185 0 : psEntry->pszName,
186 : static_cast<GUIntBig>(nOffset64));
187 : }
188 106 : anOffsets.push_back(nOffset64);
189 : }
190 :
191 3 : if (bVerbose)
192 0 : printf(" %s: checking chunks can be independently "
193 : "decompressed...\n",
194 0 : psEntry->pszName);
195 :
196 : const char *pszStartDataOffset =
197 3 : CSLFetchNameValue(papszMD, "START_DATA_OFFSET");
198 : const vsi_l_offset nStartOffset =
199 3 : std::strtoull(pszStartDataOffset, nullptr, 10);
200 3 : VSILFILE *fp = VSIFOpenL(osFilenameInZip.c_str(), "rb");
201 3 : if (!fp)
202 : {
203 0 : bSeekOptimizedValid = false;
204 0 : fprintf(stderr, "Error: cannot open %s\n",
205 : osFilenameInZip.c_str());
206 : }
207 6 : std::vector<GByte> abyData;
208 : try
209 : {
210 3 : abyData.resize(nChunkSize);
211 : }
212 0 : catch (const std::exception &)
213 : {
214 0 : fprintf(stderr, "Cannot allocate memory for chunk data.\n");
215 0 : ret = 1;
216 : }
217 109 : for (int i = 0; fp != nullptr && i < nChunksItems; ++i)
218 : {
219 106 : if (VSIFSeekL(fpRaw, nStartOffset + anOffsets[i] - 9,
220 106 : SEEK_SET) != 0)
221 : {
222 0 : fprintf(stderr, "VSIFSeekL() failed.\n");
223 0 : ret = 1;
224 : }
225 106 : GByte abyEnd[9] = {0};
226 106 : if (VSIFReadL(abyEnd, 9, 1, fpRaw) != 1)
227 : {
228 0 : fprintf(stderr, "VSIFReadL() failed.\n");
229 0 : ret = 1;
230 : }
231 106 : if (memcmp(abyEnd, "\x00\x00\xFF\xFF\x00\x00\x00\xFF\xFF",
232 : 9) != 0)
233 : {
234 0 : bSeekOptimizedValid = false;
235 0 : fprintf(
236 : stderr,
237 : "Error: file %s, chunk[%d] is not terminated by "
238 : "\\x00\\x00\\xFF\\xFF\\x00\\x00\\x00\\xFF\\xFF.\n",
239 0 : psEntry->pszName, i);
240 : }
241 106 : if (!abyData.empty())
242 : {
243 212 : if (VSIFSeekL(fp,
244 106 : static_cast<vsi_l_offset>(i) * nChunkSize,
245 106 : SEEK_SET) != 0)
246 : {
247 0 : fprintf(stderr, "VSIFSeekL() failed.\n");
248 0 : ret = 1;
249 : }
250 : const size_t nRead =
251 106 : VSIFReadL(&abyData[0], 1, nChunkSize, fp);
252 106 : if (nRead != static_cast<size_t>(nChunkSize))
253 : {
254 0 : bSeekOptimizedValid = false;
255 0 : fprintf(stderr,
256 : "Error: file %s, chunk[%d] cannot be fully "
257 : "read.\n",
258 0 : psEntry->pszName, i);
259 : }
260 : }
261 : }
262 :
263 3 : if (fp)
264 : {
265 6 : if (VSIFSeekL(fp,
266 3 : static_cast<vsi_l_offset>(nChunksItems) *
267 3 : nChunkSize,
268 3 : SEEK_SET) != 0)
269 : {
270 0 : fprintf(stderr, "VSIFSeekL() failed.\n");
271 0 : ret = 1;
272 : }
273 : const size_t nRead =
274 3 : VSIFReadL(&abyData[0], 1, nChunkSize, fp);
275 3 : if (nRead != static_cast<size_t>(
276 3 : nUncompressedSize -
277 3 : static_cast<vsi_l_offset>(nChunksItems) *
278 3 : nChunkSize))
279 : {
280 0 : bSeekOptimizedValid = false;
281 0 : fprintf(
282 : stderr,
283 : "Error: file %s, chunk[%d] cannot be fully read.\n",
284 0 : psEntry->pszName, nChunksItems);
285 : }
286 :
287 3 : VSIFCloseL(fp);
288 : }
289 :
290 3 : VSIFCloseL(fpRaw);
291 : }
292 :
293 47 : if (bSeekOptimizedValid)
294 : {
295 3 : printf("* File %s has a valid SOZip index, using chunk_size = "
296 : "%s.\n",
297 3 : psEntry->pszName, pszChunkSize);
298 3 : nCountValidSOZIP++;
299 : }
300 44 : else if (bSeekOptimizedFound)
301 : {
302 0 : fprintf(stderr,
303 : "* File %s has a SOZip index, but is is invalid!\n",
304 0 : psEntry->pszName);
305 0 : nCountInvalidSOZIP++;
306 0 : ret = 1;
307 : }
308 47 : CSLDestroy(papszMD);
309 : }
310 48 : }
311 :
312 2 : VSICloseDir(psDir);
313 :
314 2 : if (ret == 0)
315 : {
316 2 : if (nCountValidSOZIP > 0)
317 : {
318 2 : printf("-----\n");
319 2 : printf("%s is a valid .zip file, and contains %d SOZip-enabled "
320 : "file(s).\n",
321 : pszZipFilename, nCountValidSOZIP);
322 : }
323 : else
324 0 : printf("%s is a valid .zip file, but does not contain any "
325 : "SOZip-enabled files.\n",
326 : pszZipFilename);
327 : }
328 : else
329 : {
330 0 : if (nCountInvalidSOZIP > 0)
331 0 : printf("-----\n");
332 0 : fprintf(stderr, "%s is not a valid SOZip file!\n", pszZipFilename);
333 : }
334 :
335 2 : return ret;
336 : }
337 :
338 : /************************************************************************/
339 : /* main() */
340 : /************************************************************************/
341 :
342 9 : MAIN_START(nArgc, papszArgv)
343 : {
344 9 : EarlySetConfigOptions(nArgc, papszArgv);
345 9 : nArgc = GDALGeneralCmdLineProcessor(nArgc, &papszArgv, 0);
346 17 : CPLStringList aosArgv;
347 9 : aosArgv.Assign(papszArgv, /* bTakeOwnership= */ true);
348 9 : if (nArgc < 1)
349 0 : std::exit(-nArgc);
350 :
351 26 : GDALArgumentParser argParser(aosArgv[0], /* bForBinary=*/true);
352 :
353 9 : argParser.add_description(_("Generate a seek-optimized ZIP (SOZip) file."));
354 :
355 : argParser.add_epilog(
356 9 : _("For more details, consult https://gdal.org/programs/sozip.html"));
357 :
358 17 : std::string osZipFilename;
359 9 : argParser.add_argument("zip_filename")
360 18 : .metavar("<zip_filename>")
361 9 : .store_into(osZipFilename)
362 9 : .help(_("ZIP filename."));
363 :
364 9 : bool bRecurse = false;
365 9 : argParser.add_argument("-r", "--recurse-paths")
366 9 : .store_into(bRecurse)
367 : .help(_("Travels the directory structure of the specified directories "
368 9 : "recursively."));
369 :
370 9 : bool bOverwrite = false;
371 : {
372 9 : auto &group = argParser.add_mutually_exclusive_group();
373 9 : group.add_argument("-g", "--grow")
374 9 : .flag() // Default mode. Nothing to do
375 : .help(
376 : _("Grow an existing zip file with the content of the specified "
377 9 : "filename(s). Default mode."));
378 9 : group.add_argument("--overwrite")
379 9 : .store_into(bOverwrite)
380 9 : .help(_("Overwrite the target zip file if it already exists."));
381 : }
382 :
383 9 : bool bList = false;
384 9 : bool bValidate = false;
385 17 : std::string osOptimizeFrom;
386 17 : std::vector<std::string> aosFiles;
387 : {
388 9 : auto &group = argParser.add_mutually_exclusive_group();
389 9 : group.add_argument("-l", "--list")
390 9 : .store_into(bList)
391 9 : .help(_("List the files contained in the zip file."));
392 9 : group.add_argument("--validate")
393 9 : .store_into(bValidate)
394 9 : .help(_("Validates a ZIP/SOZip file."));
395 9 : group.add_argument("--optimize-from")
396 18 : .metavar("<input.zip>")
397 9 : .store_into(osOptimizeFrom)
398 : .help(
399 9 : _("Re-process {input.zip} to generate a SOZip-optimized .zip"));
400 9 : group.add_argument("input_files")
401 18 : .metavar("<input_files>")
402 9 : .store_into(aosFiles)
403 18 : .help(_("Filename of the file to add."))
404 9 : .nargs(argparse::nargs_pattern::any);
405 : }
406 :
407 9 : bool bQuiet = false;
408 9 : bool bVerbose = false;
409 9 : argParser.add_group("Advanced options");
410 : {
411 9 : auto &group = argParser.add_mutually_exclusive_group();
412 9 : group.add_argument("--quiet").store_into(bQuiet).help(
413 : _("Quiet mode. No progress message is emitted on the standard "
414 9 : "output."));
415 9 : group.add_argument("--verbose")
416 9 : .store_into(bVerbose)
417 9 : .help(_("Verbose mode."));
418 : }
419 9 : bool bJunkPaths = false;
420 9 : argParser.add_argument("-j", "--junk-paths")
421 9 : .store_into(bJunkPaths)
422 : .help(
423 : _("Store just the name of a saved file (junk the path), and do not "
424 9 : "store directory names."));
425 :
426 17 : CPLStringList aosOptions;
427 9 : argParser.add_argument("--enable-sozip")
428 9 : .choices("auto", "yes", "no")
429 18 : .metavar("auto|yes|no")
430 3 : .action([&aosOptions](const std::string &s)
431 12 : { aosOptions.SetNameValue("SOZIP_ENABLED", s.c_str()); })
432 : .help(_("In auto mode, a file is seek-optimized only if its size is "
433 : "above the value of\n"
434 : "--sozip-chunk-size. In yes mode, all input files will be "
435 : "seek-optimized.\n"
436 9 : "In no mode, no input files will be seek-optimized."));
437 9 : argParser.add_argument("--sozip-chunk-size")
438 18 : .metavar("<value in bytes or with K/M suffix>")
439 3 : .action([&aosOptions](const std::string &s)
440 12 : { aosOptions.SetNameValue("SOZIP_CHUNK_SIZE", s.c_str()); })
441 : .help(_(
442 9 : "Chunk size for a seek-optimized file. Defaults to 32768 bytes."));
443 9 : argParser.add_argument("--sozip-min-file-size")
444 18 : .metavar("<value in bytes or with K/M/G suffix>")
445 0 : .action([&aosOptions](const std::string &s)
446 9 : { aosOptions.SetNameValue("SOZIP_MIN_FILE_SIZE", s.c_str()); })
447 : .help(
448 : _("Minimum file size to decide if a file should be seek-optimized. "
449 9 : "Defaults to 1 MB byte."));
450 9 : argParser.add_argument("--content-type")
451 18 : .metavar("<string>")
452 1 : .action([&aosOptions](const std::string &s)
453 10 : { aosOptions.SetNameValue("CONTENT_TYPE", s.c_str()); })
454 9 : .help(_("Store the Content-Type for the file being added."));
455 :
456 : try
457 : {
458 9 : argParser.parse_args(aosArgv);
459 : }
460 0 : catch (const std::exception &err)
461 : {
462 0 : argParser.display_error_and_usage(err);
463 0 : std::exit(1);
464 : }
465 :
466 8 : if (!bList && !bValidate && osOptimizeFrom.empty() && aosFiles.empty())
467 : {
468 0 : std::cerr << _("Missing source filename(s)") << std::endl << std::endl;
469 0 : std::cerr << argParser << std::endl;
470 0 : std::exit(1);
471 : }
472 :
473 8 : const char *pszZipFilename = osZipFilename.c_str();
474 8 : if (!EQUAL(CPLGetExtension(pszZipFilename), "zip"))
475 : {
476 0 : std::cerr << _("Extension of zip filename should be .zip") << std::endl
477 0 : << std::endl;
478 0 : std::cerr << argParser << std::endl;
479 0 : std::exit(1);
480 : }
481 :
482 8 : if (bValidate)
483 : {
484 2 : return Validate(pszZipFilename, bVerbose);
485 : }
486 :
487 6 : if (bList)
488 : {
489 1 : VSIDIR *psDir = VSIOpenDir(
490 2 : (std::string("/vsizip/") + pszZipFilename).c_str(), -1, nullptr);
491 1 : if (psDir == nullptr)
492 0 : return 1;
493 1 : printf(" Length DateTime Seek-optimized / chunk size "
494 : "Name Properties\n");
495 : /* clang-format off */
496 1 : printf("----------- ------------------- --------------------------- ----------------- --------------\n");
497 : /* clang-format on */
498 2 : while (auto psEntry = VSIGetNextDirEntry(psDir))
499 : {
500 1 : if (!VSI_ISDIR(psEntry->nMode))
501 : {
502 : struct tm brokenDown;
503 1 : CPLUnixTimeToYMDHMS(psEntry->nMTime, &brokenDown);
504 2 : const std::string osFilename = std::string("/vsizip/{") +
505 2 : pszZipFilename + "}/" +
506 3 : psEntry->pszName;
507 2 : std::string osProperties;
508 : const CPLStringList aosMDGeneric(
509 2 : VSIGetFileMetadata(osFilename.c_str(), nullptr, nullptr));
510 1 : for (const char *pszMDGeneric : aosMDGeneric)
511 : {
512 0 : if (!osProperties.empty())
513 0 : osProperties += ',';
514 0 : osProperties += pszMDGeneric;
515 : }
516 :
517 : const CPLStringList aosMD(
518 2 : VSIGetFileMetadata(osFilename.c_str(), "ZIP", nullptr));
519 : const bool bSeekOptimized =
520 1 : aosMD.FetchNameValue("SOZIP_VALID") != nullptr;
521 : const char *pszChunkSize =
522 1 : aosMD.FetchNameValue("SOZIP_CHUNK_SIZE");
523 1 : printf("%11" CPL_FRMT_GB_WITHOUT_PREFIX
524 : "u %04d-%02d-%02d %02d:%02d:%02d %s %s "
525 : "%s\n",
526 1 : static_cast<GUIntBig>(psEntry->nSize),
527 1 : brokenDown.tm_year + 1900, brokenDown.tm_mon + 1,
528 : brokenDown.tm_mday, brokenDown.tm_hour,
529 : brokenDown.tm_min, brokenDown.tm_sec,
530 : bSeekOptimized
531 1 : ? CPLSPrintf(" yes (%9s bytes) ", pszChunkSize)
532 : : " ",
533 1 : psEntry->pszName, osProperties.c_str());
534 : }
535 1 : }
536 1 : VSICloseDir(psDir);
537 1 : return 0;
538 : }
539 :
540 : VSIStatBufL sBuf;
541 10 : CPLStringList aosOptionsCreateZip;
542 5 : if (bOverwrite)
543 : {
544 1 : VSIUnlink(pszZipFilename);
545 : }
546 : else
547 : {
548 4 : if (VSIStatExL(pszZipFilename, &sBuf, VSI_STAT_EXISTS_FLAG) == 0)
549 : {
550 1 : if (!osOptimizeFrom.empty())
551 : {
552 0 : fprintf(
553 : stderr,
554 : "%s already exists. Use --overwrite or delete it before.\n",
555 : pszZipFilename);
556 0 : return 1;
557 : }
558 1 : aosOptionsCreateZip.SetNameValue("APPEND", "TRUE");
559 : }
560 : }
561 :
562 5 : uint64_t nTotalSize = 0;
563 10 : std::vector<uint64_t> anFileSizes;
564 :
565 10 : std::string osRemovePrefix;
566 5 : if (!osOptimizeFrom.empty())
567 : {
568 1 : VSIDIR *psDir = VSIOpenDir(
569 2 : (std::string("/vsizip/") + osOptimizeFrom).c_str(), -1, nullptr);
570 1 : if (psDir == nullptr)
571 : {
572 0 : fprintf(stderr, "%s is not a valid .zip file\n",
573 : osOptimizeFrom.c_str());
574 0 : return 1;
575 : }
576 :
577 : osRemovePrefix =
578 1 : std::string("/vsizip/{").append(osOptimizeFrom).append("}/");
579 48 : while (auto psEntry = VSIGetNextDirEntry(psDir))
580 : {
581 47 : if (!VSI_ISDIR(psEntry->nMode))
582 : {
583 : const std::string osFilenameInZip =
584 92 : osRemovePrefix + psEntry->pszName;
585 46 : aosFiles.push_back(osFilenameInZip);
586 : }
587 47 : }
588 1 : VSICloseDir(psDir);
589 : }
590 4 : else if (bRecurse)
591 : {
592 0 : std::vector<std::string> aosNewFiles;
593 0 : for (const std::string &osFile : aosFiles)
594 : {
595 0 : if (VSIStatL(osFile.c_str(), &sBuf) == 0 && VSI_ISDIR(sBuf.st_mode))
596 : {
597 0 : VSIDIR *psDir = VSIOpenDir(osFile.c_str(), -1, nullptr);
598 0 : if (psDir == nullptr)
599 0 : return 1;
600 0 : while (auto psEntry = VSIGetNextDirEntry(psDir))
601 : {
602 0 : if (!VSI_ISDIR(psEntry->nMode))
603 : {
604 0 : std::string osName(osFile);
605 0 : if (osName.back() != '/')
606 0 : osName += '/';
607 0 : osName += psEntry->pszName;
608 0 : aosNewFiles.push_back(osName);
609 0 : if (aosNewFiles.size() > 10 * 1000 * 1000)
610 : {
611 0 : CPLError(CE_Failure, CPLE_NotSupported,
612 : "Too many source files");
613 0 : VSICloseDir(psDir);
614 0 : return 1;
615 : }
616 : }
617 0 : }
618 0 : VSICloseDir(psDir);
619 : }
620 : }
621 0 : aosFiles = std::move(aosNewFiles);
622 : }
623 :
624 5 : if (!bVerbose && !bQuiet)
625 : {
626 5 : anFileSizes.resize(aosFiles.size());
627 55 : for (size_t i = 0; i < aosFiles.size(); ++i)
628 : {
629 50 : if (VSIStatL(aosFiles[i].c_str(), &sBuf) == 0)
630 : {
631 50 : anFileSizes[i] = sBuf.st_size;
632 50 : nTotalSize += sBuf.st_size;
633 : }
634 : else
635 : {
636 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s\n",
637 0 : aosFiles[i].c_str());
638 0 : return 1;
639 : }
640 : }
641 : }
642 :
643 5 : void *hZIP = CPLCreateZip(pszZipFilename, aosOptionsCreateZip.List());
644 :
645 5 : if (!hZIP)
646 0 : return 1;
647 :
648 5 : uint64_t nCurSize = 0;
649 55 : for (size_t i = 0; i < aosFiles.size(); ++i)
650 : {
651 50 : if (bVerbose)
652 0 : printf("Adding %s... (%d/%d)\n", aosFiles[i].c_str(), int(i + 1),
653 0 : static_cast<int>(aosFiles.size()));
654 50 : void *pScaledProgress = nullptr;
655 50 : if (!bVerbose && !bQuiet && nTotalSize != 0)
656 : {
657 50 : pScaledProgress = GDALCreateScaledProgress(
658 50 : double(nCurSize) / nTotalSize,
659 50 : double(nCurSize + anFileSizes[i]) / nTotalSize,
660 : GDALTermProgress, nullptr);
661 : }
662 0 : else if (!bQuiet)
663 : {
664 0 : GDALTermProgress(0, nullptr, nullptr);
665 : }
666 100 : if (VSIStatL(aosFiles[i].c_str(), &sBuf) != 0 ||
667 50 : VSI_ISDIR(sBuf.st_mode))
668 : {
669 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s is not a regular file",
670 0 : aosFiles[i].c_str());
671 0 : CPLCloseZip(hZIP);
672 0 : return 1;
673 : }
674 :
675 50 : std::string osArchiveFilename(aosFiles[i]);
676 50 : if (bJunkPaths)
677 : {
678 4 : osArchiveFilename = CPLGetFilename(aosFiles[i].c_str());
679 : }
680 92 : else if (!osRemovePrefix.empty() &&
681 46 : STARTS_WITH(osArchiveFilename.c_str(), osRemovePrefix.c_str()))
682 : {
683 46 : osArchiveFilename = osArchiveFilename.substr(osRemovePrefix.size());
684 : }
685 0 : else if (osArchiveFilename[0] == '/')
686 : {
687 0 : osArchiveFilename = osArchiveFilename.substr(1);
688 : }
689 0 : else if (osArchiveFilename.size() > 3 && osArchiveFilename[1] == ':' &&
690 0 : (osArchiveFilename[2] == '/' || osArchiveFilename[2] == '\\'))
691 : {
692 0 : osArchiveFilename = osArchiveFilename.substr(3);
693 : }
694 :
695 : CPLErr eErr =
696 100 : CPLAddFileInZip(hZIP, osArchiveFilename.c_str(),
697 50 : aosFiles[i].c_str(), nullptr, aosOptions.List(),
698 : pScaledProgress ? GDALScaledProgress
699 0 : : bQuiet ? nullptr
700 : : GDALTermProgress,
701 : pScaledProgress ? pScaledProgress : nullptr);
702 50 : if (pScaledProgress)
703 : {
704 50 : GDALDestroyScaledProgress(pScaledProgress);
705 50 : nCurSize += anFileSizes[i];
706 : }
707 50 : if (eErr != CE_None)
708 : {
709 0 : CPLError(CE_Failure, CPLE_AppDefined, "Failed adding %s",
710 0 : aosFiles[i].c_str());
711 0 : CPLCloseZip(hZIP);
712 0 : return 1;
713 : }
714 : }
715 5 : CPLCloseZip(hZIP);
716 5 : return 0;
717 : }
718 :
719 0 : MAIN_END
|