| 364 | } // anonymous namespace |
| 365 | |
| 366 | Expected<void> compressZip( const std::filesystem::path& zipFile, const std::filesystem::path& sourceFolder, const CompressZipSettings& settings ) |
| 367 | { |
| 368 | MR_TIMER; |
| 369 | |
| 370 | if ( !reportProgress( settings.cb, 0.0f ) ) |
| 371 | return unexpectedOperationCanceled(); |
| 372 | |
| 373 | std::error_code ec; |
| 374 | if ( !std::filesystem::is_directory( sourceFolder, ec ) ) |
| 375 | return unexpected( "Directory '" + utf8string( sourceFolder ) + "' does not exist" ); |
| 376 | |
| 377 | int err; |
| 378 | AutoCloseZip zip( utf8string( zipFile ).c_str(), ZIP_CREATE | ZIP_TRUNCATE, &err, subprogress( settings.cb, 0.8f, 1.0f ) ); |
| 379 | if ( !zip ) |
| 380 | return unexpected( "Cannot create zip, error code: " + std::to_string( err ) ); |
| 381 | |
| 382 | auto goodFile = [&]( const std::filesystem::path & path ) |
| 383 | { |
| 384 | if ( !is_regular_file( path, ec ) ) |
| 385 | return false; |
| 386 | auto excluded = std::find_if( settings.excludeFiles.begin(), settings.excludeFiles.end(), [&] ( const auto& a ) |
| 387 | { |
| 388 | return std::filesystem::equivalent( a, path, ec ); |
| 389 | } ); |
| 390 | return excluded == settings.excludeFiles.end(); |
| 391 | }; |
| 392 | |
| 393 | // pass #1: add directories in the archive and collect the files to compress |
| 394 | std::vector<std::pair<std::filesystem::path, std::string>> files; |
| 395 | for ( auto entry : DirectoryRecursive{ sourceFolder, ec } ) |
| 396 | { |
| 397 | const auto path = entry.path(); |
| 398 | if ( entry.is_directory( ec ) && path != sourceFolder ) |
| 399 | { |
| 400 | auto archiveDirPath = utf8string( std::filesystem::relative( path, sourceFolder, ec ) ); |
| 401 | // convert folder separators in Linux style for the latest 7-zip to open archive correctly |
| 402 | std::replace( archiveDirPath.begin(), archiveDirPath.end(), '\\', '/' ); |
| 403 | if ( zip_dir_add( zip, archiveDirPath.c_str(), ZIP_FL_ENC_UTF_8 ) == -1 ) |
| 404 | return unexpected( "Cannot add directory " + archiveDirPath + " to archive" ); |
| 405 | continue; |
| 406 | } |
| 407 | |
| 408 | if ( goodFile( path ) ) |
| 409 | { |
| 410 | auto archiveFilePath = utf8string( std::filesystem::relative( path, sourceFolder, ec ) ); |
| 411 | // convert folder separators in Linux style for the latest 7-zip to open archive correctly |
| 412 | std::replace( archiveFilePath.begin(), archiveFilePath.end(), '\\', '/' ); |
| 413 | files.emplace_back( path, std::move( archiveFilePath ) ); |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | // pass #2: deflate each file in parallel, then hand libzip pre-deflated bytes via a source |
| 418 | // callback — libzip's trust-source fast path copies them into the archive without recompressing. |
| 419 | // level 0 (the settings-level "use default") normalizes to 6 — zlib's internal default is 6, |
| 420 | // and APPNOTE has no sentinel for "default", so 6 is what the archive entry records |
| 421 | const int level = settings.compressionLevel == 0 ? 6 : std::clamp( settings.compressionLevel, 1, 9 ); |
| 422 | |
| 423 | // Phase A — parallel: read each file and deflate it into entries[i]. The first worker |