7zip-native 0.0.8 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -14
- package/bench/compare.mts +25 -11
- package/bench/folder-concurrency.mts +5 -12
- package/bench/multiprocess-extract.cjs +334 -0
- package/dist/index.cjs +215 -110
- package/dist/index.d.cts +10 -9
- package/dist/index.d.mts +10 -9
- package/dist/index.d.ts +10 -9
- package/dist/index.mjs +217 -112
- package/native/addon.cpp +1399 -71
- package/package.json +4 -2
- package/patches/deflate-input-buffer-size.patch +47 -0
- package/prebuilds/darwin-arm64/7zip_native.node +0 -0
- package/prebuilds/darwin-arm64/lib7z.dylib +0 -0
- package/prebuilds/darwin-x64/7zip_native.node +0 -0
- package/prebuilds/darwin-x64/lib7z.dylib +0 -0
- package/prebuilds/linux-arm/7z.so +0 -0
- package/prebuilds/linux-arm/7zip_native.node +0 -0
- package/prebuilds/linux-arm64/7z.so +0 -0
- package/prebuilds/linux-arm64/7zip_native.node +0 -0
- package/prebuilds/linux-ia32/7z.so +0 -0
- package/prebuilds/linux-ia32/7zip_native.node +0 -0
- package/prebuilds/linux-x64/7z.so +0 -0
- package/prebuilds/linux-x64/7zip_native.node +0 -0
- package/prebuilds/win32-arm64/7z.dll +0 -0
- package/prebuilds/win32-arm64/7zip_native.node +0 -0
- package/prebuilds/win32-x64/7z.dll +0 -0
- package/prebuilds/win32-x64/7zip_native.node +0 -0
- package/scripts/build-native.mjs +22 -1
- package/vendor/7zip/CPP/7zip/Archive/Archive2.def +1 -0
- package/vendor/7zip/CPP/7zip/Bundles/Format7zF/_o/7z.so +0 -0
- package/vendor/7zip/CPP/7zip/Bundles/Format7zF/_o/Alloc.o +0 -0
- package/vendor/7zip/CPP/7zip/Bundles/Format7zF/_o/DeflateDecoder.o +0 -0
- package/vendor/7zip/CPP/7zip/Compress/DeflateDecoder.cpp +15 -1
package/native/addon.cpp
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
#include <algorithm>
|
|
4
4
|
#include <atomic>
|
|
5
5
|
#include <cctype>
|
|
6
|
+
#include <chrono>
|
|
6
7
|
#include <cmath>
|
|
7
8
|
#include <codecvt>
|
|
8
9
|
#include <condition_variable>
|
|
9
10
|
#include <cstdint>
|
|
10
11
|
#include <cstring>
|
|
12
|
+
#include <cstdio>
|
|
13
|
+
#include <cstdlib>
|
|
11
14
|
#include <cwctype>
|
|
12
15
|
#include <deque>
|
|
13
16
|
#include <filesystem>
|
|
@@ -27,6 +30,13 @@
|
|
|
27
30
|
|
|
28
31
|
#ifndef _WIN32
|
|
29
32
|
#include <dlfcn.h>
|
|
33
|
+
#include <cerrno>
|
|
34
|
+
#include <fcntl.h>
|
|
35
|
+
#include <unistd.h>
|
|
36
|
+
#else
|
|
37
|
+
#include <fcntl.h>
|
|
38
|
+
#include <io.h>
|
|
39
|
+
#include <sys/stat.h>
|
|
30
40
|
#endif
|
|
31
41
|
|
|
32
42
|
#include "Common/MyInitGuid.h"
|
|
@@ -45,11 +55,14 @@ constexpr std::uint64_t kMaxSingleOutputBytes = 256ULL * 1024 * 1024;
|
|
|
45
55
|
constexpr std::uint64_t kMaxConcurrentOutputBytes = 512ULL * 1024 * 1024;
|
|
46
56
|
constexpr std::uint64_t kMaxReadBytes = 64ULL * 1024 * 1024;
|
|
47
57
|
constexpr std::size_t kMaxPasswordBytes = 1024 * 1024;
|
|
58
|
+
constexpr std::uint32_t kMinDeflateBufferSize = 4 * 1024;
|
|
59
|
+
constexpr std::uint32_t kMaxDeflateBufferSize = 2 * 1024 * 1024;
|
|
48
60
|
|
|
49
61
|
using CreateObjectFunction = HRESULT (STDAPICALLTYPE *)(const GUID *, const GUID *, void **);
|
|
50
62
|
using GetHandlerPropertyFunction = HRESULT (STDAPICALLTYPE *)(UInt32, PROPID, PROPVARIANT *);
|
|
51
63
|
using GetNumberOfFormatsFunction = HRESULT (STDAPICALLTYPE *)(UInt32 *);
|
|
52
64
|
using GetIsArcFunction = HRESULT (STDAPICALLTYPE *)(UInt32, Func_IsArc *);
|
|
65
|
+
using SetDeflateInputBufferSizeFunction = HRESULT (STDAPICALLTYPE *)(UInt32);
|
|
53
66
|
|
|
54
67
|
std::wstring FromUtf8(const std::string& value);
|
|
55
68
|
|
|
@@ -63,10 +76,13 @@ std::atomic<std::uint64_t> g_pendingOperations{0};
|
|
|
63
76
|
std::atomic<std::uint32_t> g_extractionConcurrency{1};
|
|
64
77
|
std::atomic<std::uint64_t> g_nextHandle{1};
|
|
65
78
|
std::atomic<std::uint64_t> g_nextStreamId{1};
|
|
79
|
+
std::atomic<std::uint64_t> g_nextFileTaskId{1};
|
|
66
80
|
std::mutex g_handlesMutex;
|
|
67
81
|
std::unordered_map<std::uint64_t, std::shared_ptr<class ArchiveEngine>> g_handles;
|
|
68
82
|
std::mutex g_streamsMutex;
|
|
69
83
|
std::unordered_map<std::uint64_t, std::shared_ptr<class NativeStreamState>> g_streams;
|
|
84
|
+
std::mutex g_fileTasksMutex;
|
|
85
|
+
std::unordered_map<std::uint64_t, std::shared_ptr<class NativeFileTaskState>> g_fileTasks;
|
|
70
86
|
std::mutex g_outputBudgetMutex;
|
|
71
87
|
std::condition_variable g_outputBudgetAvailable;
|
|
72
88
|
std::uint64_t g_reservedOutputBytes = 0;
|
|
@@ -77,6 +93,7 @@ CreateObjectFunction g_createObject = nullptr;
|
|
|
77
93
|
GetHandlerPropertyFunction g_getHandlerProperty = nullptr;
|
|
78
94
|
GetNumberOfFormatsFunction g_getNumberOfFormats = nullptr;
|
|
79
95
|
GetIsArcFunction g_getIsArc = nullptr;
|
|
96
|
+
SetDeflateInputBufferSizeFunction g_setDeflateInputBufferSize = nullptr;
|
|
80
97
|
class ExtractionPool;
|
|
81
98
|
ExtractionPool *g_extractionPool = nullptr;
|
|
82
99
|
|
|
@@ -93,6 +110,9 @@ Function LoadEngineFunction(const char *name)
|
|
|
93
110
|
std::uint64_t RegisterStream(std::shared_ptr<class NativeStreamState> stream);
|
|
94
111
|
std::shared_ptr<class NativeStreamState> FindStream(std::uint64_t streamId);
|
|
95
112
|
void RemoveStream(std::uint64_t streamId);
|
|
113
|
+
std::uint64_t RegisterFileTask(std::shared_ptr<class NativeFileTaskState> task);
|
|
114
|
+
std::shared_ptr<class NativeFileTaskState> FindFileTask(std::uint64_t taskId);
|
|
115
|
+
void RemoveFileTask(std::uint64_t taskId);
|
|
96
116
|
|
|
97
117
|
void EnsureEngineLoaded(const std::string& path)
|
|
98
118
|
{
|
|
@@ -122,8 +142,11 @@ void EnsureEngineLoaded(const std::string& path)
|
|
|
122
142
|
g_getHandlerProperty = LoadEngineFunction<GetHandlerPropertyFunction>("GetHandlerProperty2");
|
|
123
143
|
g_getNumberOfFormats = LoadEngineFunction<GetNumberOfFormatsFunction>("GetNumberOfFormats");
|
|
124
144
|
g_getIsArc = LoadEngineFunction<GetIsArcFunction>("GetIsArc");
|
|
145
|
+
g_setDeflateInputBufferSize =
|
|
146
|
+
LoadEngineFunction<SetDeflateInputBufferSizeFunction>("SetDeflateInputBufferSize");
|
|
125
147
|
if (g_createObject == nullptr || g_getHandlerProperty == nullptr
|
|
126
|
-
|| g_getNumberOfFormats == nullptr || g_getIsArc == nullptr
|
|
148
|
+
|| g_getNumberOfFormats == nullptr || g_getIsArc == nullptr
|
|
149
|
+
|| g_setDeflateInputBufferSize == nullptr) {
|
|
127
150
|
#ifdef _WIN32
|
|
128
151
|
::FreeLibrary(g_engineModule);
|
|
129
152
|
#else
|
|
@@ -134,6 +157,7 @@ void EnsureEngineLoaded(const std::string& path)
|
|
|
134
157
|
g_getHandlerProperty = nullptr;
|
|
135
158
|
g_getNumberOfFormats = nullptr;
|
|
136
159
|
g_getIsArc = nullptr;
|
|
160
|
+
g_setDeflateInputBufferSize = nullptr;
|
|
137
161
|
throw std::runtime_error("The bundled 7-Zip engine is missing required exports");
|
|
138
162
|
}
|
|
139
163
|
g_enginePath = path;
|
|
@@ -198,6 +222,40 @@ struct StreamMessage {
|
|
|
198
222
|
};
|
|
199
223
|
|
|
200
224
|
void StreamMessageToJs(Napi::Env env, Napi::Function callback, StreamMessage *message);
|
|
225
|
+
class FileCompletionAcknowledgement {
|
|
226
|
+
public:
|
|
227
|
+
void Acknowledge()
|
|
228
|
+
{
|
|
229
|
+
{
|
|
230
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
231
|
+
acknowledged_ = true;
|
|
232
|
+
}
|
|
233
|
+
completed_.notify_one();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
void Wait()
|
|
237
|
+
{
|
|
238
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
239
|
+
completed_.wait(lock, [this] { return acknowledged_; });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private:
|
|
243
|
+
std::mutex mutex_;
|
|
244
|
+
std::condition_variable completed_;
|
|
245
|
+
bool acknowledged_ = false;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
struct FileCompletionMessage {
|
|
249
|
+
enum class Kind {
|
|
250
|
+
File,
|
|
251
|
+
Barrier,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
Kind kind;
|
|
255
|
+
std::uint32_t entryId;
|
|
256
|
+
std::shared_ptr<FileCompletionAcknowledgement> acknowledgement;
|
|
257
|
+
};
|
|
258
|
+
void FileCompletionToJs(Napi::Env env, Napi::Function callback, FileCompletionMessage *message);
|
|
201
259
|
|
|
202
260
|
class NativeStreamState {
|
|
203
261
|
public:
|
|
@@ -267,6 +325,93 @@ private:
|
|
|
267
325
|
std::atomic<bool> callbackReleased_{false};
|
|
268
326
|
};
|
|
269
327
|
|
|
328
|
+
class NativeFileTaskState {
|
|
329
|
+
public:
|
|
330
|
+
void Cancel()
|
|
331
|
+
{
|
|
332
|
+
cancelled_.store(true, std::memory_order_release);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
bool IsCancelled() const
|
|
336
|
+
{
|
|
337
|
+
return cancelled_.load(std::memory_order_acquire);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
void Fail(std::string error)
|
|
341
|
+
{
|
|
342
|
+
{
|
|
343
|
+
std::lock_guard<std::mutex> lock(failureMutex_);
|
|
344
|
+
if (failure_.empty()) {
|
|
345
|
+
failure_ = std::move(error);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
Cancel();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
std::string Failure() const
|
|
352
|
+
{
|
|
353
|
+
std::lock_guard<std::mutex> lock(failureMutex_);
|
|
354
|
+
return failure_;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
bool ReportFileCompleted(std::uint32_t entryId)
|
|
358
|
+
{
|
|
359
|
+
if (!hasFileCompletionCallback_) {
|
|
360
|
+
return !IsCancelled();
|
|
361
|
+
}
|
|
362
|
+
auto *message = new FileCompletionMessage{
|
|
363
|
+
FileCompletionMessage::Kind::File,
|
|
364
|
+
entryId,
|
|
365
|
+
{},
|
|
366
|
+
};
|
|
367
|
+
const auto status = fileCompletionCallback.BlockingCall(message, FileCompletionToJs);
|
|
368
|
+
if (status != napi_ok) {
|
|
369
|
+
delete message;
|
|
370
|
+
Cancel();
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
return !IsCancelled();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
bool WaitForFileCompletions()
|
|
377
|
+
{
|
|
378
|
+
if (!hasFileCompletionCallback_) {
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
auto acknowledgement = std::make_shared<FileCompletionAcknowledgement>();
|
|
382
|
+
auto *message = new FileCompletionMessage{
|
|
383
|
+
FileCompletionMessage::Kind::Barrier,
|
|
384
|
+
0,
|
|
385
|
+
acknowledgement,
|
|
386
|
+
};
|
|
387
|
+
const auto status = fileCompletionCallback.BlockingCall(message, FileCompletionToJs);
|
|
388
|
+
if (status != napi_ok) {
|
|
389
|
+
delete message;
|
|
390
|
+
Cancel();
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
acknowledgement->Wait();
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
void ReleaseCallback()
|
|
398
|
+
{
|
|
399
|
+
if (hasFileCompletionCallback_
|
|
400
|
+
&& !fileCompletionCallbackReleased_.exchange(true, std::memory_order_acq_rel)) {
|
|
401
|
+
fileCompletionCallback.Release();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
std::uint64_t id = 0;
|
|
405
|
+
Napi::ThreadSafeFunction fileCompletionCallback;
|
|
406
|
+
bool hasFileCompletionCallback_ = false;
|
|
407
|
+
|
|
408
|
+
private:
|
|
409
|
+
std::atomic<bool> cancelled_{false};
|
|
410
|
+
std::atomic<bool> fileCompletionCallbackReleased_{false};
|
|
411
|
+
mutable std::mutex failureMutex_;
|
|
412
|
+
std::string failure_;
|
|
413
|
+
};
|
|
414
|
+
|
|
270
415
|
std::uint64_t RegisterStream(std::shared_ptr<NativeStreamState> stream)
|
|
271
416
|
{
|
|
272
417
|
const auto streamId = g_nextStreamId.fetch_add(1, std::memory_order_relaxed);
|
|
@@ -292,6 +437,31 @@ void RemoveStream(std::uint64_t streamId)
|
|
|
292
437
|
g_streams.erase(streamId);
|
|
293
438
|
}
|
|
294
439
|
|
|
440
|
+
std::uint64_t RegisterFileTask(std::shared_ptr<NativeFileTaskState> task)
|
|
441
|
+
{
|
|
442
|
+
const auto taskId = g_nextFileTaskId.fetch_add(1, std::memory_order_relaxed);
|
|
443
|
+
if (taskId == 0) {
|
|
444
|
+
throw std::overflow_error("Native file task handle space exhausted");
|
|
445
|
+
}
|
|
446
|
+
std::lock_guard<std::mutex> lock(g_fileTasksMutex);
|
|
447
|
+
task->id = taskId;
|
|
448
|
+
g_fileTasks.emplace(taskId, std::move(task));
|
|
449
|
+
return taskId;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
std::shared_ptr<NativeFileTaskState> FindFileTask(std::uint64_t taskId)
|
|
453
|
+
{
|
|
454
|
+
std::lock_guard<std::mutex> lock(g_fileTasksMutex);
|
|
455
|
+
const auto found = g_fileTasks.find(taskId);
|
|
456
|
+
return found == g_fileTasks.end() ? nullptr : found->second;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
void RemoveFileTask(std::uint64_t taskId)
|
|
460
|
+
{
|
|
461
|
+
std::lock_guard<std::mutex> lock(g_fileTasksMutex);
|
|
462
|
+
g_fileTasks.erase(taskId);
|
|
463
|
+
}
|
|
464
|
+
|
|
295
465
|
FileStamp GetFileStamp(const std::string& path)
|
|
296
466
|
{
|
|
297
467
|
std::error_code error;
|
|
@@ -489,8 +659,26 @@ struct Entry {
|
|
|
489
659
|
struct ArchiveCreateItem {
|
|
490
660
|
std::string sourcePath;
|
|
491
661
|
std::string path;
|
|
662
|
+
double mtimeMs = 0;
|
|
492
663
|
};
|
|
493
664
|
|
|
665
|
+
FILETIME UnixMillisecondsToFileTime(double milliseconds)
|
|
666
|
+
{
|
|
667
|
+
constexpr long double windowsEpochOffsetMs = 11644473600000.0L;
|
|
668
|
+
constexpr long double ticksPerMillisecond = 10000.0L;
|
|
669
|
+
const long double ticks =
|
|
670
|
+
(static_cast<long double>(milliseconds) + windowsEpochOffsetMs) * ticksPerMillisecond;
|
|
671
|
+
if (!std::isfinite(milliseconds) || ticks < 0
|
|
672
|
+
|| ticks > static_cast<long double>(std::numeric_limits<UInt64>::max())) {
|
|
673
|
+
throw std::out_of_range("Source file modification time is outside the FILETIME range");
|
|
674
|
+
}
|
|
675
|
+
const auto value = static_cast<UInt64>(ticks);
|
|
676
|
+
FILETIME fileTime{};
|
|
677
|
+
fileTime.dwLowDateTime = static_cast<DWORD>(value);
|
|
678
|
+
fileTime.dwHighDateTime = static_cast<DWORD>(value >> 32);
|
|
679
|
+
return fileTime;
|
|
680
|
+
}
|
|
681
|
+
|
|
494
682
|
std::uint64_t GetPropUInt64(IInArchive *archive, std::uint32_t index, PROPID propId)
|
|
495
683
|
{
|
|
496
684
|
NWindows::NCOM::CPropVariant value;
|
|
@@ -521,6 +709,14 @@ bool GetPropBool(IInArchive *archive, std::uint32_t index, PROPID propId)
|
|
|
521
709
|
return value.boolVal != VARIANT_FALSE;
|
|
522
710
|
}
|
|
523
711
|
|
|
712
|
+
bool IsSolidArchive(IInArchive *archive)
|
|
713
|
+
{
|
|
714
|
+
NWindows::NCOM::CPropVariant value;
|
|
715
|
+
return archive->GetArchiveProperty(kpidSolid, &value) == S_OK
|
|
716
|
+
&& value.vt == VT_BOOL
|
|
717
|
+
&& value.boolVal != VARIANT_FALSE;
|
|
718
|
+
}
|
|
719
|
+
|
|
524
720
|
std::string GetPropString(IInArchive *archive, std::uint32_t index, PROPID propId)
|
|
525
721
|
{
|
|
526
722
|
NWindows::NCOM::CPropVariant value;
|
|
@@ -536,10 +732,32 @@ struct OutputRequest {
|
|
|
536
732
|
std::uint64_t length = 0;
|
|
537
733
|
std::uint64_t entrySize = 0;
|
|
538
734
|
bool encrypted = false;
|
|
735
|
+
bool abortOnLimit = false;
|
|
736
|
+
bool limitReached = false;
|
|
539
737
|
std::vector<std::uint8_t> data;
|
|
540
738
|
std::int32_t operationResult = NArchive::NExtract::NOperationResult::kUnavailable;
|
|
541
739
|
};
|
|
542
740
|
|
|
741
|
+
struct ReadRange {
|
|
742
|
+
std::uint64_t offset;
|
|
743
|
+
std::uint64_t length;
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
struct ExtractionProfile {
|
|
747
|
+
double validationMs = 0;
|
|
748
|
+
double reservationMs = 0;
|
|
749
|
+
double requestSetupMs = 0;
|
|
750
|
+
double contextAcquireMs = 0;
|
|
751
|
+
double solidCheckMs = 0;
|
|
752
|
+
double callbackSetupMs = 0;
|
|
753
|
+
double sevenZipExtractMs = 0;
|
|
754
|
+
double resultProcessingMs = 0;
|
|
755
|
+
double minSevenZipCallMs = 0;
|
|
756
|
+
double maxSevenZipCallMs = 0;
|
|
757
|
+
std::uint32_t sevenZipCalls = 0;
|
|
758
|
+
bool abortPerItem = false;
|
|
759
|
+
};
|
|
760
|
+
|
|
543
761
|
class FileInStream final : public IInStream, public CMyUnknownImp {
|
|
544
762
|
Z7_COM_QI_BEGIN2(IInStream)
|
|
545
763
|
Z7_COM_QI_ENTRY(ISequentialInStream)
|
|
@@ -891,6 +1109,8 @@ Z7_COM7F_IMF(ArchiveUpdateCallback::GetProperty(
|
|
|
891
1109
|
property = path.c_str();
|
|
892
1110
|
} else if (propertyId == kpidIsDir) {
|
|
893
1111
|
property = false;
|
|
1112
|
+
} else if (propertyId == kpidMTime) {
|
|
1113
|
+
property = UnixMillisecondsToFileTime(item.mtimeMs);
|
|
894
1114
|
} else if (propertyId == kpidSize) {
|
|
895
1115
|
std::error_code error;
|
|
896
1116
|
const auto size = std::filesystem::file_size(NativePath(item.sourcePath), error);
|
|
@@ -1046,6 +1266,13 @@ Z7_COM7F_IMF(VectorOutStream::Write(const void *data, UInt32 size, UInt32 *proce
|
|
|
1046
1266
|
bytes + static_cast<std::size_t>(copyEnd - start));
|
|
1047
1267
|
}
|
|
1048
1268
|
seen_ = end;
|
|
1269
|
+
if (request_->abortOnLimit && request_->data.size() == request_->length) {
|
|
1270
|
+
request_->limitReached = true;
|
|
1271
|
+
if (processedSize != nullptr) {
|
|
1272
|
+
*processedSize = 0;
|
|
1273
|
+
}
|
|
1274
|
+
return E_ABORT;
|
|
1275
|
+
}
|
|
1049
1276
|
if (processedSize != nullptr) {
|
|
1050
1277
|
*processedSize = size;
|
|
1051
1278
|
}
|
|
@@ -1130,7 +1357,9 @@ Z7_COM7F_IMF(ExtractCallback::PrepareOperation(Int32))
|
|
|
1130
1357
|
Z7_COM7F_IMF(ExtractCallback::SetOperationResult(Int32 operationResult))
|
|
1131
1358
|
{
|
|
1132
1359
|
if (currentRequest_ != nullptr) {
|
|
1133
|
-
currentRequest_->operationResult =
|
|
1360
|
+
currentRequest_->operationResult = currentRequest_->limitReached
|
|
1361
|
+
? NArchive::NExtract::NOperationResult::kOK
|
|
1362
|
+
: operationResult;
|
|
1134
1363
|
}
|
|
1135
1364
|
return S_OK;
|
|
1136
1365
|
}
|
|
@@ -1201,67 +1430,657 @@ public:
|
|
|
1201
1430
|
std::uint32_t id,
|
|
1202
1431
|
std::shared_ptr<NativeStreamState> stream,
|
|
1203
1432
|
const std::optional<std::string>& password)
|
|
1204
|
-
: id_(id),
|
|
1205
|
-
stream_(std::move(stream)),
|
|
1433
|
+
: id_(id),
|
|
1434
|
+
stream_(std::move(stream)),
|
|
1435
|
+
password_(password),
|
|
1436
|
+
operationResult_(NArchive::NExtract::NOperationResult::kUnavailable),
|
|
1437
|
+
passwordRequested_(false) {}
|
|
1438
|
+
|
|
1439
|
+
std::int32_t OperationResult() const
|
|
1440
|
+
{
|
|
1441
|
+
return operationResult_;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
bool PasswordRequested() const
|
|
1445
|
+
{
|
|
1446
|
+
return passwordRequested_.load(std::memory_order_acquire);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
private:
|
|
1450
|
+
std::uint32_t id_;
|
|
1451
|
+
std::shared_ptr<NativeStreamState> stream_;
|
|
1452
|
+
const std::optional<std::string>& password_;
|
|
1453
|
+
std::int32_t operationResult_;
|
|
1454
|
+
std::vector<CMyComPtr<ISequentialOutStream>> streams_;
|
|
1455
|
+
std::atomic<bool> passwordRequested_;
|
|
1456
|
+
};
|
|
1457
|
+
|
|
1458
|
+
Z7_COM7F_IMF(StreamingExtractCallback::SetTotal(UInt64))
|
|
1459
|
+
{
|
|
1460
|
+
return S_OK;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
Z7_COM7F_IMF(StreamingExtractCallback::SetCompleted(const UInt64 *))
|
|
1464
|
+
{
|
|
1465
|
+
return S_OK;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
Z7_COM7F_IMF(StreamingExtractCallback::GetStream(
|
|
1469
|
+
UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode))
|
|
1470
|
+
{
|
|
1471
|
+
*outStream = nullptr;
|
|
1472
|
+
if (askExtractMode != NArchive::NExtract::NAskMode::kExtract || index != id_) {
|
|
1473
|
+
return S_OK;
|
|
1474
|
+
}
|
|
1475
|
+
CMyComPtr<ISequentialOutStream> stream = new StreamingOutStream(stream_);
|
|
1476
|
+
streams_.push_back(stream);
|
|
1477
|
+
*outStream = stream;
|
|
1478
|
+
(*outStream)->AddRef();
|
|
1479
|
+
return S_OK;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
Z7_COM7F_IMF(StreamingExtractCallback::PrepareOperation(Int32))
|
|
1483
|
+
{
|
|
1484
|
+
return S_OK;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
Z7_COM7F_IMF(StreamingExtractCallback::SetOperationResult(Int32 operationResult))
|
|
1488
|
+
{
|
|
1489
|
+
operationResult_ = operationResult;
|
|
1490
|
+
return S_OK;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
Z7_COM7F_IMF(StreamingExtractCallback::CryptoGetTextPassword(BSTR *password))
|
|
1494
|
+
{
|
|
1495
|
+
passwordRequested_.store(true, std::memory_order_release);
|
|
1496
|
+
return GetPasswordBstr(password_, password);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
class NativeFileOutputState {
|
|
1500
|
+
public:
|
|
1501
|
+
NativeFileOutputState(std::uint32_t id, std::string path, std::uint64_t expectedSize, bool overwrite)
|
|
1502
|
+
: id_(id),
|
|
1503
|
+
path_(std::move(path)),
|
|
1504
|
+
expectedSize_(expectedSize),
|
|
1505
|
+
overwrite_(overwrite) {}
|
|
1506
|
+
|
|
1507
|
+
~NativeFileOutputState()
|
|
1508
|
+
{
|
|
1509
|
+
Abort();
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
bool Open()
|
|
1513
|
+
{
|
|
1514
|
+
if (overwrite_ && !RemoveOutputFile()) {
|
|
1515
|
+
return false;
|
|
1516
|
+
}
|
|
1517
|
+
#ifdef _WIN32
|
|
1518
|
+
const auto nativePath = NativePath(path_);
|
|
1519
|
+
fileDescriptor_ = ::_wopen(
|
|
1520
|
+
nativePath.c_str(),
|
|
1521
|
+
_O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY,
|
|
1522
|
+
_S_IREAD | _S_IWRITE);
|
|
1523
|
+
#else
|
|
1524
|
+
const auto nativePath = NativePath(path_);
|
|
1525
|
+
int flags = O_WRONLY | O_CREAT | O_EXCL;
|
|
1526
|
+
#ifdef O_CLOEXEC
|
|
1527
|
+
flags |= O_CLOEXEC;
|
|
1528
|
+
#endif
|
|
1529
|
+
#ifdef O_NOFOLLOW
|
|
1530
|
+
flags |= O_NOFOLLOW;
|
|
1531
|
+
#endif
|
|
1532
|
+
fileDescriptor_ = ::open(nativePath.c_str(), flags, 0600);
|
|
1533
|
+
#endif
|
|
1534
|
+
if (fileDescriptor_ < 0) {
|
|
1535
|
+
SetError("Unable to create extraction output file");
|
|
1536
|
+
return false;
|
|
1537
|
+
}
|
|
1538
|
+
fileCreated_ = true;
|
|
1539
|
+
return true;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
bool Write(const std::vector<std::uint8_t>& data)
|
|
1543
|
+
{
|
|
1544
|
+
std::size_t offset = 0;
|
|
1545
|
+
while (offset < data.size()) {
|
|
1546
|
+
#ifdef _WIN32
|
|
1547
|
+
const auto written = ::_write(
|
|
1548
|
+
fileDescriptor_,
|
|
1549
|
+
data.data() + offset,
|
|
1550
|
+
static_cast<unsigned int>(data.size() - offset));
|
|
1551
|
+
#else
|
|
1552
|
+
const auto written = ::write(
|
|
1553
|
+
fileDescriptor_,
|
|
1554
|
+
data.data() + offset,
|
|
1555
|
+
data.size() - offset);
|
|
1556
|
+
#endif
|
|
1557
|
+
if (written < 0) {
|
|
1558
|
+
#ifndef _WIN32
|
|
1559
|
+
if (errno == EINTR) {
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
#endif
|
|
1563
|
+
SetError("Unable to write extraction output file");
|
|
1564
|
+
return false;
|
|
1565
|
+
}
|
|
1566
|
+
if (written == 0) {
|
|
1567
|
+
SetError("Unable to write extraction output file");
|
|
1568
|
+
return false;
|
|
1569
|
+
}
|
|
1570
|
+
const auto count = static_cast<std::size_t>(written);
|
|
1571
|
+
offset += count;
|
|
1572
|
+
bytesWritten_ += count;
|
|
1573
|
+
}
|
|
1574
|
+
return true;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
bool Close()
|
|
1578
|
+
{
|
|
1579
|
+
if (fileDescriptor_ < 0) {
|
|
1580
|
+
return error_.empty();
|
|
1581
|
+
}
|
|
1582
|
+
#ifdef _WIN32
|
|
1583
|
+
const auto result = ::_close(fileDescriptor_);
|
|
1584
|
+
#else
|
|
1585
|
+
const auto result = ::close(fileDescriptor_);
|
|
1586
|
+
#endif
|
|
1587
|
+
fileDescriptor_ = -1;
|
|
1588
|
+
if (result != 0) {
|
|
1589
|
+
SetError("Unable to close extraction output file");
|
|
1590
|
+
return false;
|
|
1591
|
+
}
|
|
1592
|
+
return true;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
void Abort()
|
|
1596
|
+
{
|
|
1597
|
+
Close();
|
|
1598
|
+
if (fileCreated_ && !completed_) {
|
|
1599
|
+
RemoveOutputFile();
|
|
1600
|
+
fileCreated_ = false;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
void MarkCompleted()
|
|
1605
|
+
{
|
|
1606
|
+
completed_ = true;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
void AddQueuedBytes(std::uint64_t bytes)
|
|
1610
|
+
{
|
|
1611
|
+
bytesQueued_ += bytes;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
void MarkSizeLimitExceeded()
|
|
1615
|
+
{
|
|
1616
|
+
sizeLimitExceeded_ = true;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
std::uint32_t Id() const { return id_; }
|
|
1620
|
+
std::uint64_t ExpectedSize() const { return expectedSize_; }
|
|
1621
|
+
std::uint64_t BytesQueued() const { return bytesQueued_; }
|
|
1622
|
+
std::uint64_t BytesWritten() const { return bytesWritten_; }
|
|
1623
|
+
bool Completed() const { return completed_; }
|
|
1624
|
+
bool SizeLimitExceeded() const { return sizeLimitExceeded_; }
|
|
1625
|
+
const std::string& Error() const { return error_; }
|
|
1626
|
+
|
|
1627
|
+
private:
|
|
1628
|
+
void SetError(const char *message)
|
|
1629
|
+
{
|
|
1630
|
+
if (error_.empty()) {
|
|
1631
|
+
error_ = std::string(message) + ": " + std::strerror(errno);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
bool RemoveOutputFile()
|
|
1636
|
+
{
|
|
1637
|
+
const auto nativePath = NativePath(path_);
|
|
1638
|
+
#ifdef _WIN32
|
|
1639
|
+
const auto result = ::_wunlink(nativePath.c_str());
|
|
1640
|
+
#else
|
|
1641
|
+
const auto result = ::unlink(nativePath.c_str());
|
|
1642
|
+
#endif
|
|
1643
|
+
if (result == 0 || errno == ENOENT) {
|
|
1644
|
+
return true;
|
|
1645
|
+
}
|
|
1646
|
+
SetError("Unable to remove extraction output file");
|
|
1647
|
+
return false;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
std::uint32_t id_;
|
|
1651
|
+
std::string path_;
|
|
1652
|
+
int fileDescriptor_ = -1;
|
|
1653
|
+
std::uint64_t expectedSize_;
|
|
1654
|
+
bool overwrite_;
|
|
1655
|
+
bool fileCreated_ = false;
|
|
1656
|
+
bool completed_ = false;
|
|
1657
|
+
std::uint64_t bytesQueued_ = 0;
|
|
1658
|
+
std::uint64_t bytesWritten_ = 0;
|
|
1659
|
+
bool sizeLimitExceeded_ = false;
|
|
1660
|
+
std::string error_;
|
|
1661
|
+
};
|
|
1662
|
+
|
|
1663
|
+
class AsyncFileWriter {
|
|
1664
|
+
public:
|
|
1665
|
+
explicit AsyncFileWriter(std::shared_ptr<NativeFileTaskState> task)
|
|
1666
|
+
: task_(std::move(task)) {}
|
|
1667
|
+
|
|
1668
|
+
~AsyncFileWriter()
|
|
1669
|
+
{
|
|
1670
|
+
StopAndJoin();
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
void Start()
|
|
1674
|
+
{
|
|
1675
|
+
worker_ = std::thread([this] { Run(); });
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
bool EnqueueWrite(
|
|
1679
|
+
const std::shared_ptr<NativeFileOutputState>& output,
|
|
1680
|
+
const std::uint8_t *data,
|
|
1681
|
+
std::size_t size)
|
|
1682
|
+
{
|
|
1683
|
+
try {
|
|
1684
|
+
std::vector<std::uint8_t> copy(data, data + size);
|
|
1685
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
1686
|
+
spaceAvailable_.wait(lock, [this, size] {
|
|
1687
|
+
return stopping_
|
|
1688
|
+
|| task_->IsCancelled()
|
|
1689
|
+
|| (HasQueueSpace() && queuedBytes_ + size <= kMaxQueuedBytes);
|
|
1690
|
+
});
|
|
1691
|
+
if (stopping_ || task_->IsCancelled()) {
|
|
1692
|
+
return false;
|
|
1693
|
+
}
|
|
1694
|
+
queue_.push_back({Job::Kind::Write, output, std::move(copy)});
|
|
1695
|
+
queuedBytes_ += size;
|
|
1696
|
+
} catch (const std::exception& error) {
|
|
1697
|
+
task_->Fail(error.what());
|
|
1698
|
+
return false;
|
|
1699
|
+
} catch (...) {
|
|
1700
|
+
task_->Fail("Unable to queue extraction output data");
|
|
1701
|
+
return false;
|
|
1702
|
+
}
|
|
1703
|
+
workAvailable_.notify_one();
|
|
1704
|
+
return true;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
bool EnqueueFinish(const std::shared_ptr<NativeFileOutputState>& output)
|
|
1708
|
+
{
|
|
1709
|
+
try {
|
|
1710
|
+
{
|
|
1711
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
1712
|
+
spaceAvailable_.wait(lock, [this] {
|
|
1713
|
+
return stopping_ || HasQueueSpace();
|
|
1714
|
+
});
|
|
1715
|
+
if (stopping_) {
|
|
1716
|
+
return false;
|
|
1717
|
+
}
|
|
1718
|
+
queue_.push_back({Job::Kind::Finish, output, {}});
|
|
1719
|
+
}
|
|
1720
|
+
workAvailable_.notify_one();
|
|
1721
|
+
return true;
|
|
1722
|
+
} catch (const std::exception& error) {
|
|
1723
|
+
task_->Fail(error.what());
|
|
1724
|
+
return false;
|
|
1725
|
+
} catch (...) {
|
|
1726
|
+
task_->Fail("Unable to queue extraction output completion");
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
bool EnqueueAbort(const std::shared_ptr<NativeFileOutputState>& output) noexcept
|
|
1732
|
+
{
|
|
1733
|
+
try {
|
|
1734
|
+
{
|
|
1735
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
1736
|
+
spaceAvailable_.wait(lock, [this] {
|
|
1737
|
+
return stopping_ || HasQueueSpace();
|
|
1738
|
+
});
|
|
1739
|
+
if (stopping_) {
|
|
1740
|
+
return false;
|
|
1741
|
+
}
|
|
1742
|
+
queue_.push_back({Job::Kind::Abort, output, {}});
|
|
1743
|
+
}
|
|
1744
|
+
workAvailable_.notify_one();
|
|
1745
|
+
return true;
|
|
1746
|
+
} catch (...) {
|
|
1747
|
+
return false;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
void WaitUntilIdle()
|
|
1752
|
+
{
|
|
1753
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
1754
|
+
idle_.wait(lock, [this] { return queue_.empty() && !active_; });
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
void StopAndJoin()
|
|
1758
|
+
{
|
|
1759
|
+
{
|
|
1760
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
1761
|
+
stopping_ = true;
|
|
1762
|
+
}
|
|
1763
|
+
workAvailable_.notify_all();
|
|
1764
|
+
spaceAvailable_.notify_all();
|
|
1765
|
+
if (worker_.joinable()) {
|
|
1766
|
+
worker_.join();
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
private:
|
|
1771
|
+
struct Job {
|
|
1772
|
+
enum class Kind {
|
|
1773
|
+
Write,
|
|
1774
|
+
Finish,
|
|
1775
|
+
Abort,
|
|
1776
|
+
};
|
|
1777
|
+
|
|
1778
|
+
Kind kind;
|
|
1779
|
+
std::shared_ptr<NativeFileOutputState> output;
|
|
1780
|
+
std::vector<std::uint8_t> data;
|
|
1781
|
+
};
|
|
1782
|
+
|
|
1783
|
+
static constexpr std::size_t kMaxQueuedBytes = 8 * 1024 * 1024;
|
|
1784
|
+
static constexpr std::size_t kMaxQueuedJobs = 1024;
|
|
1785
|
+
|
|
1786
|
+
bool HasQueueSpace() const
|
|
1787
|
+
{
|
|
1788
|
+
return queue_.size() + (active_ ? 1 : 0) < kMaxQueuedJobs;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
void Run() noexcept
|
|
1792
|
+
{
|
|
1793
|
+
while (true) {
|
|
1794
|
+
Job job;
|
|
1795
|
+
{
|
|
1796
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
1797
|
+
workAvailable_.wait(lock, [this] {
|
|
1798
|
+
return stopping_ || !queue_.empty();
|
|
1799
|
+
});
|
|
1800
|
+
if (queue_.empty() && stopping_) {
|
|
1801
|
+
idle_.notify_all();
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
job = std::move(queue_.front());
|
|
1805
|
+
queue_.pop_front();
|
|
1806
|
+
active_ = true;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
try {
|
|
1810
|
+
Process(job);
|
|
1811
|
+
} catch (const std::exception& error) {
|
|
1812
|
+
task_->Fail(error.what());
|
|
1813
|
+
job.output->Abort();
|
|
1814
|
+
} catch (...) {
|
|
1815
|
+
task_->Fail("Unknown asynchronous extraction output failure");
|
|
1816
|
+
job.output->Abort();
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
{
|
|
1820
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
1821
|
+
if (job.kind == Job::Kind::Write) {
|
|
1822
|
+
queuedBytes_ -= job.data.size();
|
|
1823
|
+
}
|
|
1824
|
+
active_ = false;
|
|
1825
|
+
if (queue_.empty()) {
|
|
1826
|
+
idle_.notify_all();
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
spaceAvailable_.notify_all();
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
void Process(const Job& job)
|
|
1834
|
+
{
|
|
1835
|
+
if (job.kind == Job::Kind::Abort) {
|
|
1836
|
+
job.output->Abort();
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
if (job.kind == Job::Kind::Write) {
|
|
1840
|
+
if (task_->IsCancelled()) {
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
if (!job.output->Write(job.data)) {
|
|
1844
|
+
const auto& error = job.output->Error();
|
|
1845
|
+
task_->Fail(error.empty() ? "Unable to write extraction output file" : error);
|
|
1846
|
+
job.output->Abort();
|
|
1847
|
+
}
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
if (task_->IsCancelled()) {
|
|
1851
|
+
job.output->Abort();
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
if (!job.output->Close()) {
|
|
1855
|
+
const auto& error = job.output->Error();
|
|
1856
|
+
task_->Fail(error.empty() ? "Unable to close extraction output file" : error);
|
|
1857
|
+
job.output->Abort();
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
job.output->MarkCompleted();
|
|
1861
|
+
task_->ReportFileCompleted(job.output->Id());
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
std::shared_ptr<NativeFileTaskState> task_;
|
|
1865
|
+
std::mutex mutex_;
|
|
1866
|
+
std::condition_variable workAvailable_;
|
|
1867
|
+
std::condition_variable spaceAvailable_;
|
|
1868
|
+
std::condition_variable idle_;
|
|
1869
|
+
std::deque<Job> queue_;
|
|
1870
|
+
std::size_t queuedBytes_ = 0;
|
|
1871
|
+
bool active_ = false;
|
|
1872
|
+
bool stopping_ = false;
|
|
1873
|
+
std::thread worker_;
|
|
1874
|
+
};
|
|
1875
|
+
|
|
1876
|
+
class NativeFileOutStream final : public ISequentialOutStream, public CMyUnknownImp {
|
|
1877
|
+
Z7_COM_UNKNOWN_IMP_1(ISequentialOutStream)
|
|
1878
|
+
Z7_COM7F_IMP(Write(const void *data, UInt32 size, UInt32 *processedSize))
|
|
1879
|
+
|
|
1880
|
+
public:
|
|
1881
|
+
NativeFileOutStream(
|
|
1882
|
+
std::shared_ptr<NativeFileOutputState> output,
|
|
1883
|
+
AsyncFileWriter& writer,
|
|
1884
|
+
std::shared_ptr<NativeFileTaskState> task)
|
|
1885
|
+
: output_(std::move(output)),
|
|
1886
|
+
writer_(writer),
|
|
1887
|
+
task_(std::move(task)) {}
|
|
1888
|
+
|
|
1889
|
+
~NativeFileOutStream()
|
|
1890
|
+
{
|
|
1891
|
+
if (!terminalQueued_) {
|
|
1892
|
+
writer_.EnqueueAbort(output_);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
bool Finish()
|
|
1897
|
+
{
|
|
1898
|
+
if (terminalQueued_) {
|
|
1899
|
+
return true;
|
|
1900
|
+
}
|
|
1901
|
+
terminalQueued_ = writer_.EnqueueFinish(output_);
|
|
1902
|
+
return terminalQueued_;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
void Abort()
|
|
1906
|
+
{
|
|
1907
|
+
if (!terminalQueued_) {
|
|
1908
|
+
terminalQueued_ = writer_.EnqueueAbort(output_);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
private:
|
|
1913
|
+
std::shared_ptr<NativeFileOutputState> output_;
|
|
1914
|
+
AsyncFileWriter& writer_;
|
|
1915
|
+
std::shared_ptr<NativeFileTaskState> task_;
|
|
1916
|
+
bool terminalQueued_ = false;
|
|
1917
|
+
};
|
|
1918
|
+
|
|
1919
|
+
Z7_COM7F_IMF(NativeFileOutStream::Write(
|
|
1920
|
+
const void *data, UInt32 size, UInt32 *processedSize))
|
|
1921
|
+
{
|
|
1922
|
+
if (processedSize != nullptr) {
|
|
1923
|
+
*processedSize = 0;
|
|
1924
|
+
}
|
|
1925
|
+
if (task_->IsCancelled()) {
|
|
1926
|
+
return E_ABORT;
|
|
1927
|
+
}
|
|
1928
|
+
if (size > output_->ExpectedSize() - std::min(
|
|
1929
|
+
output_->ExpectedSize(), output_->BytesQueued())) {
|
|
1930
|
+
output_->MarkSizeLimitExceeded();
|
|
1931
|
+
return E_ABORT;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
const auto *bytes = static_cast<const std::uint8_t *>(data);
|
|
1935
|
+
std::uint32_t offset = 0;
|
|
1936
|
+
while (offset < size) {
|
|
1937
|
+
if (task_->IsCancelled()) {
|
|
1938
|
+
return E_ABORT;
|
|
1939
|
+
}
|
|
1940
|
+
const auto chunkSize = std::min<std::uint32_t>(64 * 1024, size - offset);
|
|
1941
|
+
if (!writer_.EnqueueWrite(output_, bytes + offset, chunkSize)) {
|
|
1942
|
+
return task_->IsCancelled() ? E_ABORT : E_FAIL;
|
|
1943
|
+
}
|
|
1944
|
+
output_->AddQueuedBytes(chunkSize);
|
|
1945
|
+
offset += chunkSize;
|
|
1946
|
+
}
|
|
1947
|
+
if (processedSize != nullptr) {
|
|
1948
|
+
*processedSize = size;
|
|
1949
|
+
}
|
|
1950
|
+
return S_OK;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
struct NativeFileOutputSpec {
|
|
1954
|
+
std::uint32_t id = 0;
|
|
1955
|
+
std::string path;
|
|
1956
|
+
bool overwrite = false;
|
|
1957
|
+
};
|
|
1958
|
+
|
|
1959
|
+
struct FileOutputRequest {
|
|
1960
|
+
std::uint32_t id = 0;
|
|
1961
|
+
std::string path;
|
|
1962
|
+
std::uint64_t expectedSize = 0;
|
|
1963
|
+
bool overwrite = false;
|
|
1964
|
+
std::int32_t operationResult = NArchive::NExtract::NOperationResult::kUnavailable;
|
|
1965
|
+
NativeFileOutStream *stream = nullptr;
|
|
1966
|
+
std::shared_ptr<NativeFileOutputState> output;
|
|
1967
|
+
std::string error;
|
|
1968
|
+
};
|
|
1969
|
+
|
|
1970
|
+
class BatchFileExtractCallback final :
|
|
1971
|
+
public IArchiveExtractCallback,
|
|
1972
|
+
public ICryptoGetTextPassword,
|
|
1973
|
+
public CMyUnknownImp {
|
|
1974
|
+
Z7_COM_QI_BEGIN2(IArchiveExtractCallback)
|
|
1975
|
+
Z7_COM_QI_ENTRY(IProgress)
|
|
1976
|
+
Z7_COM_QI_ENTRY(ICryptoGetTextPassword)
|
|
1977
|
+
Z7_COM_QI_END
|
|
1978
|
+
Z7_COM_ADDREF_RELEASE
|
|
1979
|
+
|
|
1980
|
+
Z7_IFACE_COM7_IMP(IProgress)
|
|
1981
|
+
Z7_IFACE_COM7_IMP(IArchiveExtractCallback)
|
|
1982
|
+
Z7_IFACE_COM7_IMP(ICryptoGetTextPassword)
|
|
1983
|
+
|
|
1984
|
+
public:
|
|
1985
|
+
BatchFileExtractCallback(
|
|
1986
|
+
std::vector<FileOutputRequest>& requests,
|
|
1987
|
+
const std::unordered_map<std::uint32_t, std::size_t>& requestById,
|
|
1988
|
+
std::shared_ptr<NativeFileTaskState> task,
|
|
1989
|
+
AsyncFileWriter& writer,
|
|
1990
|
+
const std::optional<std::string>& password)
|
|
1991
|
+
: requests_(requests),
|
|
1992
|
+
requestById_(requestById),
|
|
1993
|
+
task_(std::move(task)),
|
|
1994
|
+
writer_(writer),
|
|
1206
1995
|
password_(password),
|
|
1207
|
-
|
|
1996
|
+
currentRequest_(nullptr),
|
|
1208
1997
|
passwordRequested_(false) {}
|
|
1209
1998
|
|
|
1210
|
-
std::int32_t OperationResult() const
|
|
1211
|
-
{
|
|
1212
|
-
return operationResult_;
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
1999
|
bool PasswordRequested() const
|
|
1216
2000
|
{
|
|
1217
2001
|
return passwordRequested_.load(std::memory_order_acquire);
|
|
1218
2002
|
}
|
|
1219
2003
|
|
|
1220
2004
|
private:
|
|
1221
|
-
std::
|
|
1222
|
-
std::
|
|
2005
|
+
std::vector<FileOutputRequest>& requests_;
|
|
2006
|
+
const std::unordered_map<std::uint32_t, std::size_t>& requestById_;
|
|
2007
|
+
std::shared_ptr<NativeFileTaskState> task_;
|
|
2008
|
+
AsyncFileWriter& writer_;
|
|
1223
2009
|
const std::optional<std::string>& password_;
|
|
1224
|
-
|
|
1225
|
-
std::vector<CMyComPtr<ISequentialOutStream>> streams_;
|
|
2010
|
+
FileOutputRequest *currentRequest_;
|
|
1226
2011
|
std::atomic<bool> passwordRequested_;
|
|
2012
|
+
std::vector<CMyComPtr<ISequentialOutStream>> streams_;
|
|
1227
2013
|
};
|
|
1228
2014
|
|
|
1229
|
-
Z7_COM7F_IMF(
|
|
2015
|
+
Z7_COM7F_IMF(BatchFileExtractCallback::SetTotal(UInt64))
|
|
1230
2016
|
{
|
|
1231
2017
|
return S_OK;
|
|
1232
2018
|
}
|
|
1233
2019
|
|
|
1234
|
-
Z7_COM7F_IMF(
|
|
2020
|
+
Z7_COM7F_IMF(BatchFileExtractCallback::SetCompleted(const UInt64 *))
|
|
1235
2021
|
{
|
|
1236
|
-
return S_OK;
|
|
2022
|
+
return task_->IsCancelled() ? E_ABORT : S_OK;
|
|
1237
2023
|
}
|
|
1238
2024
|
|
|
1239
|
-
Z7_COM7F_IMF(
|
|
2025
|
+
Z7_COM7F_IMF(BatchFileExtractCallback::GetStream(
|
|
1240
2026
|
UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode))
|
|
1241
2027
|
{
|
|
1242
2028
|
*outStream = nullptr;
|
|
1243
|
-
|
|
2029
|
+
currentRequest_ = nullptr;
|
|
2030
|
+
if (task_->IsCancelled()) {
|
|
2031
|
+
return E_ABORT;
|
|
2032
|
+
}
|
|
2033
|
+
if (askExtractMode != NArchive::NExtract::NAskMode::kExtract) {
|
|
1244
2034
|
return S_OK;
|
|
1245
2035
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
2036
|
+
const auto found = requestById_.find(index);
|
|
2037
|
+
if (found == requestById_.end()) {
|
|
2038
|
+
return S_OK;
|
|
2039
|
+
}
|
|
2040
|
+
auto& request = requests_[found->second];
|
|
2041
|
+
currentRequest_ = &request;
|
|
2042
|
+
request.output = std::make_shared<NativeFileOutputState>(
|
|
2043
|
+
request.id, request.path, request.expectedSize, request.overwrite);
|
|
2044
|
+
if (!request.output->Open()) {
|
|
2045
|
+
request.error = request.output->Error();
|
|
2046
|
+
return E_FAIL;
|
|
2047
|
+
}
|
|
2048
|
+
auto *fileStream = new NativeFileOutStream(
|
|
2049
|
+
request.output, writer_, task_);
|
|
2050
|
+
CMyComPtr<ISequentialOutStream> output = fileStream;
|
|
2051
|
+
request.stream = fileStream;
|
|
2052
|
+
streams_.push_back(output);
|
|
2053
|
+
*outStream = output;
|
|
1249
2054
|
(*outStream)->AddRef();
|
|
1250
2055
|
return S_OK;
|
|
1251
2056
|
}
|
|
1252
2057
|
|
|
1253
|
-
Z7_COM7F_IMF(
|
|
2058
|
+
Z7_COM7F_IMF(BatchFileExtractCallback::PrepareOperation(Int32))
|
|
1254
2059
|
{
|
|
1255
|
-
return S_OK;
|
|
2060
|
+
return task_->IsCancelled() ? E_ABORT : S_OK;
|
|
1256
2061
|
}
|
|
1257
2062
|
|
|
1258
|
-
Z7_COM7F_IMF(
|
|
2063
|
+
Z7_COM7F_IMF(BatchFileExtractCallback::SetOperationResult(Int32 operationResult))
|
|
1259
2064
|
{
|
|
1260
|
-
|
|
2065
|
+
if (currentRequest_ != nullptr) {
|
|
2066
|
+
currentRequest_->operationResult = operationResult;
|
|
2067
|
+
if (operationResult == NArchive::NExtract::NOperationResult::kOK
|
|
2068
|
+
&& currentRequest_->stream != nullptr
|
|
2069
|
+
&& currentRequest_->error.empty()
|
|
2070
|
+
&& currentRequest_->output != nullptr
|
|
2071
|
+
&& !currentRequest_->output->SizeLimitExceeded()) {
|
|
2072
|
+
if (!currentRequest_->stream->Finish()) {
|
|
2073
|
+
currentRequest_->error = task_->Failure();
|
|
2074
|
+
return E_ABORT;
|
|
2075
|
+
}
|
|
2076
|
+
} else if (currentRequest_->stream != nullptr) {
|
|
2077
|
+
currentRequest_->stream->Abort();
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
1261
2080
|
return S_OK;
|
|
1262
2081
|
}
|
|
1263
2082
|
|
|
1264
|
-
Z7_COM7F_IMF(
|
|
2083
|
+
Z7_COM7F_IMF(BatchFileExtractCallback::CryptoGetTextPassword(BSTR *password))
|
|
1265
2084
|
{
|
|
1266
2085
|
passwordRequested_.store(true, std::memory_order_release);
|
|
1267
2086
|
return GetPasswordBstr(password_, password);
|
|
@@ -1323,11 +2142,13 @@ public:
|
|
|
1323
2142
|
void Open(
|
|
1324
2143
|
const std::string& path,
|
|
1325
2144
|
std::optional<std::string> password,
|
|
1326
|
-
const std::string& enginePath
|
|
2145
|
+
const std::string& enginePath,
|
|
2146
|
+
std::uint32_t deflateBufferSize)
|
|
1327
2147
|
{
|
|
1328
2148
|
EnsureEngineLoaded(enginePath);
|
|
1329
2149
|
path_ = path;
|
|
1330
2150
|
password_ = std::move(password);
|
|
2151
|
+
deflateBufferSize_ = deflateBufferSize;
|
|
1331
2152
|
const auto extension = Extension(path);
|
|
1332
2153
|
UInt32 formatCount = 0;
|
|
1333
2154
|
if (g_getNumberOfFormats(&formatCount) != S_OK || formatCount == 0) {
|
|
@@ -1477,17 +2298,27 @@ public:
|
|
|
1477
2298
|
|
|
1478
2299
|
std::vector<std::vector<std::uint8_t>> Extract(
|
|
1479
2300
|
const std::vector<std::uint32_t>& ids,
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
OutputReservation& outputReservation)
|
|
2301
|
+
const std::vector<ReadRange>& ranges,
|
|
2302
|
+
OutputReservation& outputReservation,
|
|
2303
|
+
ExtractionProfile *profile = nullptr)
|
|
1484
2304
|
{
|
|
2305
|
+
using Clock = std::chrono::steady_clock;
|
|
2306
|
+
const auto elapsedMs = [](Clock::time_point start) {
|
|
2307
|
+
return std::chrono::duration<double, std::milli>(Clock::now() - start).count();
|
|
2308
|
+
};
|
|
2309
|
+
Clock::time_point phaseStart;
|
|
1485
2310
|
if (contexts_.empty()) {
|
|
1486
2311
|
throw std::runtime_error("Archive is closed");
|
|
1487
2312
|
}
|
|
2313
|
+
if (!ranges.empty() && ranges.size() != ids.size()) {
|
|
2314
|
+
throw std::invalid_argument("Read ranges must match the requested entry count");
|
|
2315
|
+
}
|
|
1488
2316
|
std::unordered_map<std::uint32_t, std::size_t> requestById;
|
|
1489
2317
|
requestById.reserve(ids.size());
|
|
1490
2318
|
std::uint64_t totalRequested = 0;
|
|
2319
|
+
if (profile != nullptr) {
|
|
2320
|
+
phaseStart = Clock::now();
|
|
2321
|
+
}
|
|
1491
2322
|
for (std::size_t requestIndex = 0; requestIndex < ids.size(); ++requestIndex) {
|
|
1492
2323
|
const auto id = ids[requestIndex];
|
|
1493
2324
|
if (id >= entries_.size()) {
|
|
@@ -1497,9 +2328,10 @@ public:
|
|
|
1497
2328
|
if (entry.directory) {
|
|
1498
2329
|
throw std::invalid_argument("Cannot read a directory entry");
|
|
1499
2330
|
}
|
|
1500
|
-
const
|
|
2331
|
+
const bool partial = !ranges.empty();
|
|
2332
|
+
const auto start = partial ? std::min(ranges[requestIndex].offset, entry.size) : 0;
|
|
1501
2333
|
const auto available = entry.size - start;
|
|
1502
|
-
const auto requested = partial ? std::min(length, available) : entry.size;
|
|
2334
|
+
const auto requested = partial ? std::min(ranges[requestIndex].length, available) : entry.size;
|
|
1503
2335
|
if (!requestById.emplace(id, requestIndex).second) {
|
|
1504
2336
|
throw std::invalid_argument("Duplicate entry IDs are not allowed in a batch");
|
|
1505
2337
|
}
|
|
@@ -1509,14 +2341,24 @@ public:
|
|
|
1509
2341
|
}
|
|
1510
2342
|
totalRequested += requested;
|
|
1511
2343
|
}
|
|
2344
|
+
if (profile != nullptr) {
|
|
2345
|
+
profile->validationMs = elapsedMs(phaseStart);
|
|
2346
|
+
phaseStart = Clock::now();
|
|
2347
|
+
}
|
|
1512
2348
|
outputReservation.Acquire(totalRequested);
|
|
2349
|
+
if (profile != nullptr) {
|
|
2350
|
+
profile->reservationMs = elapsedMs(phaseStart);
|
|
2351
|
+
phaseStart = Clock::now();
|
|
2352
|
+
}
|
|
1513
2353
|
std::vector<OutputRequest> requests;
|
|
1514
2354
|
requests.reserve(ids.size());
|
|
1515
|
-
for (
|
|
2355
|
+
for (std::size_t requestIndex = 0; requestIndex < ids.size(); ++requestIndex) {
|
|
2356
|
+
const auto id = ids[requestIndex];
|
|
1516
2357
|
const auto& entry = entries_[id];
|
|
1517
|
-
const
|
|
2358
|
+
const bool partial = !ranges.empty();
|
|
2359
|
+
const auto start = partial ? std::min(ranges[requestIndex].offset, entry.size) : 0;
|
|
1518
2360
|
const auto available = entry.size - start;
|
|
1519
|
-
const auto requested = partial ? std::min(length, available) : entry.size;
|
|
2361
|
+
const auto requested = partial ? std::min(ranges[requestIndex].length, available) : entry.size;
|
|
1520
2362
|
OutputRequest request;
|
|
1521
2363
|
request.id = id;
|
|
1522
2364
|
request.offset = start;
|
|
@@ -1529,6 +2371,11 @@ public:
|
|
|
1529
2371
|
std::vector<UInt32> sortedIds;
|
|
1530
2372
|
sortedIds.reserve(requestById.size());
|
|
1531
2373
|
for (const auto& item : requestById) {
|
|
2374
|
+
auto& request = requests[item.second];
|
|
2375
|
+
if (!ranges.empty() && request.length == 0) {
|
|
2376
|
+
request.operationResult = NArchive::NExtract::NOperationResult::kOK;
|
|
2377
|
+
continue;
|
|
2378
|
+
}
|
|
1532
2379
|
sortedIds.push_back(item.first);
|
|
1533
2380
|
}
|
|
1534
2381
|
if (std::all_of(requests.begin(), requests.end(), [](const OutputRequest& request) {
|
|
@@ -1537,14 +2384,77 @@ public:
|
|
|
1537
2384
|
return std::vector<std::vector<std::uint8_t>>(ids.size());
|
|
1538
2385
|
}
|
|
1539
2386
|
std::sort(sortedIds.begin(), sortedIds.end());
|
|
2387
|
+
if (profile != nullptr) {
|
|
2388
|
+
profile->requestSetupMs = elapsedMs(phaseStart);
|
|
2389
|
+
phaseStart = Clock::now();
|
|
2390
|
+
}
|
|
1540
2391
|
auto context = AcquireContext();
|
|
2392
|
+
if (profile != nullptr) {
|
|
2393
|
+
profile->contextAcquireMs = elapsedMs(phaseStart);
|
|
2394
|
+
phaseStart = Clock::now();
|
|
2395
|
+
}
|
|
2396
|
+
const bool abortPerItem = !ranges.empty() && !IsSolidArchive(context->archive);
|
|
2397
|
+
if (profile != nullptr) {
|
|
2398
|
+
profile->solidCheckMs = elapsedMs(phaseStart);
|
|
2399
|
+
profile->abortPerItem = abortPerItem;
|
|
2400
|
+
phaseStart = Clock::now();
|
|
2401
|
+
}
|
|
2402
|
+
if (abortPerItem) {
|
|
2403
|
+
for (auto& request : requests) {
|
|
2404
|
+
request.abortOnLimit = request.offset + request.length < request.entrySize;
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
1541
2407
|
auto *extractCallbackObject = new ExtractCallback(requests, requestById, password_);
|
|
1542
2408
|
CMyComPtr<IArchiveExtractCallback> callback = extractCallbackObject;
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
2409
|
+
if (profile != nullptr) {
|
|
2410
|
+
profile->callbackSetupMs = elapsedMs(phaseStart);
|
|
2411
|
+
phaseStart = Clock::now();
|
|
2412
|
+
}
|
|
2413
|
+
HRESULT result = S_OK;
|
|
2414
|
+
if (g_setDeflateInputBufferSize(deflateBufferSize_) != S_OK) {
|
|
2415
|
+
throw std::runtime_error("Unable to configure the Deflate input buffer");
|
|
2416
|
+
}
|
|
2417
|
+
if (abortPerItem) {
|
|
2418
|
+
for (const auto id : sortedIds) {
|
|
2419
|
+
const auto requestIndex = requestById.at(id);
|
|
2420
|
+
const UInt32 itemId = id;
|
|
2421
|
+
const auto callStart = profile != nullptr ? Clock::now() : Clock::time_point{};
|
|
2422
|
+
const auto itemResult = context->archive->Extract(&itemId, 1, 0, callback);
|
|
2423
|
+
if (profile != nullptr) {
|
|
2424
|
+
const double callMs = elapsedMs(callStart);
|
|
2425
|
+
if (profile->sevenZipCalls == 0 || callMs < profile->minSevenZipCallMs) {
|
|
2426
|
+
profile->minSevenZipCallMs = callMs;
|
|
2427
|
+
}
|
|
2428
|
+
profile->maxSevenZipCallMs = std::max(profile->maxSevenZipCallMs, callMs);
|
|
2429
|
+
++profile->sevenZipCalls;
|
|
2430
|
+
}
|
|
2431
|
+
if (itemResult == E_ABORT && requests[requestIndex].limitReached) {
|
|
2432
|
+
requests[requestIndex].operationResult = NArchive::NExtract::NOperationResult::kOK;
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
if (itemResult != S_OK) {
|
|
2436
|
+
result = itemResult;
|
|
2437
|
+
break;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
} else {
|
|
2441
|
+
const auto callStart = profile != nullptr ? Clock::now() : Clock::time_point{};
|
|
2442
|
+
result = context->archive->Extract(
|
|
2443
|
+
sortedIds.empty() ? nullptr : sortedIds.data(),
|
|
2444
|
+
static_cast<UInt32>(sortedIds.size()),
|
|
2445
|
+
0,
|
|
2446
|
+
callback);
|
|
2447
|
+
if (profile != nullptr) {
|
|
2448
|
+
const double callMs = elapsedMs(callStart);
|
|
2449
|
+
profile->sevenZipCalls = 1;
|
|
2450
|
+
profile->minSevenZipCallMs = callMs;
|
|
2451
|
+
profile->maxSevenZipCallMs = callMs;
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
if (profile != nullptr) {
|
|
2455
|
+
profile->sevenZipExtractMs = elapsedMs(phaseStart);
|
|
2456
|
+
phaseStart = Clock::now();
|
|
2457
|
+
}
|
|
1548
2458
|
if (result != S_OK) {
|
|
1549
2459
|
if (extractCallbackObject->PasswordRequested()) {
|
|
1550
2460
|
throw std::runtime_error("Archive password is missing or incorrect");
|
|
@@ -1567,6 +2477,9 @@ public:
|
|
|
1567
2477
|
for (const auto id : ids) {
|
|
1568
2478
|
resultData.push_back(std::move(requests[requestById.at(id)].data));
|
|
1569
2479
|
}
|
|
2480
|
+
if (profile != nullptr) {
|
|
2481
|
+
profile->resultProcessingMs = elapsedMs(phaseStart);
|
|
2482
|
+
}
|
|
1570
2483
|
return resultData;
|
|
1571
2484
|
}
|
|
1572
2485
|
|
|
@@ -1585,6 +2498,9 @@ public:
|
|
|
1585
2498
|
auto *extractCallbackObject = new StreamingExtractCallback(id, stream, password_);
|
|
1586
2499
|
CMyComPtr<IArchiveExtractCallback> callback = extractCallbackObject;
|
|
1587
2500
|
const UInt32 itemIndex = id;
|
|
2501
|
+
if (g_setDeflateInputBufferSize(deflateBufferSize_) != S_OK) {
|
|
2502
|
+
throw std::runtime_error("Unable to configure the Deflate input buffer");
|
|
2503
|
+
}
|
|
1588
2504
|
const auto result = context->archive->Extract(&itemIndex, 1, 0, callback);
|
|
1589
2505
|
if (result != S_OK) {
|
|
1590
2506
|
if (extractCallbackObject->PasswordRequested()) {
|
|
@@ -1605,6 +2521,120 @@ public:
|
|
|
1605
2521
|
}
|
|
1606
2522
|
}
|
|
1607
2523
|
|
|
2524
|
+
void ExtractToFiles(
|
|
2525
|
+
const std::vector<NativeFileOutputSpec>& outputs,
|
|
2526
|
+
const std::shared_ptr<NativeFileTaskState>& task,
|
|
2527
|
+
AsyncFileWriter& writer)
|
|
2528
|
+
{
|
|
2529
|
+
if (contexts_.empty()) {
|
|
2530
|
+
throw std::runtime_error("Archive is closed");
|
|
2531
|
+
}
|
|
2532
|
+
if (task->IsCancelled()) {
|
|
2533
|
+
const auto failure = task->Failure();
|
|
2534
|
+
throw std::runtime_error(failure.empty()
|
|
2535
|
+
? "Native folder extraction was cancelled"
|
|
2536
|
+
: failure);
|
|
2537
|
+
}
|
|
2538
|
+
if (outputs.empty()) {
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
std::vector<FileOutputRequest> requests;
|
|
2543
|
+
requests.reserve(outputs.size());
|
|
2544
|
+
std::unordered_map<std::uint32_t, std::size_t> requestById;
|
|
2545
|
+
requestById.reserve(outputs.size());
|
|
2546
|
+
std::vector<UInt32> sortedIds;
|
|
2547
|
+
sortedIds.reserve(outputs.size());
|
|
2548
|
+
for (const auto& output : outputs) {
|
|
2549
|
+
const auto id = output.id;
|
|
2550
|
+
const auto& path = output.path;
|
|
2551
|
+
if (id >= entries_.size()) {
|
|
2552
|
+
throw std::out_of_range("Entry ID is out of range");
|
|
2553
|
+
}
|
|
2554
|
+
if (entries_[id].directory) {
|
|
2555
|
+
throw std::invalid_argument("Cannot extract a directory entry to a file");
|
|
2556
|
+
}
|
|
2557
|
+
if (path.empty() || path.find('\0') != std::string::npos) {
|
|
2558
|
+
throw std::invalid_argument("Extraction output paths must be non-empty and contain no null bytes");
|
|
2559
|
+
}
|
|
2560
|
+
if (!requestById.emplace(id, requests.size()).second) {
|
|
2561
|
+
throw std::invalid_argument("Duplicate entry IDs are not allowed in a folder extraction batch");
|
|
2562
|
+
}
|
|
2563
|
+
requests.push_back({
|
|
2564
|
+
id,
|
|
2565
|
+
path,
|
|
2566
|
+
entries_[id].size,
|
|
2567
|
+
output.overwrite,
|
|
2568
|
+
NArchive::NExtract::NOperationResult::kUnavailable,
|
|
2569
|
+
nullptr,
|
|
2570
|
+
nullptr,
|
|
2571
|
+
{},
|
|
2572
|
+
});
|
|
2573
|
+
sortedIds.push_back(id);
|
|
2574
|
+
}
|
|
2575
|
+
std::sort(sortedIds.begin(), sortedIds.end());
|
|
2576
|
+
|
|
2577
|
+
HRESULT result = E_FAIL;
|
|
2578
|
+
bool passwordRequested = false;
|
|
2579
|
+
{
|
|
2580
|
+
auto context = AcquireContext();
|
|
2581
|
+
auto *extractCallbackObject = new BatchFileExtractCallback(
|
|
2582
|
+
requests, requestById, task, writer, password_);
|
|
2583
|
+
CMyComPtr<IArchiveExtractCallback> callback = extractCallbackObject;
|
|
2584
|
+
if (g_setDeflateInputBufferSize(deflateBufferSize_) != S_OK) {
|
|
2585
|
+
throw std::runtime_error("Unable to configure the Deflate input buffer");
|
|
2586
|
+
}
|
|
2587
|
+
result = context->archive->Extract(
|
|
2588
|
+
sortedIds.data(),
|
|
2589
|
+
static_cast<UInt32>(sortedIds.size()),
|
|
2590
|
+
0,
|
|
2591
|
+
callback);
|
|
2592
|
+
passwordRequested = extractCallbackObject->PasswordRequested();
|
|
2593
|
+
}
|
|
2594
|
+
writer.WaitUntilIdle();
|
|
2595
|
+
const auto writerFailure = task->Failure();
|
|
2596
|
+
if (!writerFailure.empty()) {
|
|
2597
|
+
throw std::runtime_error(writerFailure);
|
|
2598
|
+
}
|
|
2599
|
+
if (task->IsCancelled()) {
|
|
2600
|
+
throw std::runtime_error("Native folder extraction was cancelled");
|
|
2601
|
+
}
|
|
2602
|
+
for (const auto& request : requests) {
|
|
2603
|
+
if (!request.error.empty()) {
|
|
2604
|
+
throw std::runtime_error(request.error);
|
|
2605
|
+
}
|
|
2606
|
+
if (request.output != nullptr && request.output->SizeLimitExceeded()) {
|
|
2607
|
+
throw std::runtime_error(
|
|
2608
|
+
"Extracted entry exceeds its declared size: " + request.path);
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
if (result != S_OK) {
|
|
2612
|
+
if (passwordRequested) {
|
|
2613
|
+
throw std::runtime_error("Archive password is missing or incorrect");
|
|
2614
|
+
}
|
|
2615
|
+
throw std::runtime_error("7-Zip failed to extract the requested files");
|
|
2616
|
+
}
|
|
2617
|
+
for (const auto& request : requests) {
|
|
2618
|
+
if (request.operationResult != NArchive::NExtract::NOperationResult::kOK) {
|
|
2619
|
+
if (passwordRequested) {
|
|
2620
|
+
throw std::runtime_error("Archive password is incorrect or encrypted data is invalid");
|
|
2621
|
+
}
|
|
2622
|
+
if (request.operationResult == NArchive::NExtract::NOperationResult::kWrongPassword) {
|
|
2623
|
+
throw std::runtime_error("Archive password is incorrect");
|
|
2624
|
+
}
|
|
2625
|
+
throw std::runtime_error(
|
|
2626
|
+
"Entry extraction failed (unsupported method, integrity error, or password required): "
|
|
2627
|
+
+ request.path);
|
|
2628
|
+
}
|
|
2629
|
+
if (request.output == nullptr || !request.output->Completed()) {
|
|
2630
|
+
throw std::runtime_error("Entry output did not finish writing: " + request.path);
|
|
2631
|
+
}
|
|
2632
|
+
if (request.output->BytesWritten() != request.output->BytesQueued()) {
|
|
2633
|
+
throw std::runtime_error("Entry output write count is inconsistent: " + request.path);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
|
|
1608
2638
|
private:
|
|
1609
2639
|
ContextLease AcquireContext()
|
|
1610
2640
|
{
|
|
@@ -1728,6 +2758,7 @@ private:
|
|
|
1728
2758
|
FileStamp fileStamp_{};
|
|
1729
2759
|
std::optional<std::string> password_;
|
|
1730
2760
|
std::vector<Entry> entries_;
|
|
2761
|
+
std::uint32_t deflateBufferSize_ = 32 * 1024;
|
|
1731
2762
|
};
|
|
1732
2763
|
|
|
1733
2764
|
Napi::Object EntryToJs(Napi::Env env, const Entry& entry)
|
|
@@ -1752,7 +2783,7 @@ Napi::Buffer<std::uint8_t> TakeBuffer(Napi::Env env, std::vector<std::uint8_t> d
|
|
|
1752
2783
|
return Napi::Buffer<std::uint8_t>::New(env, 0);
|
|
1753
2784
|
}
|
|
1754
2785
|
auto *owned = new std::vector<std::uint8_t>(std::move(data));
|
|
1755
|
-
return Napi::Buffer<std::uint8_t>::
|
|
2786
|
+
return Napi::Buffer<std::uint8_t>::NewOrCopy(
|
|
1756
2787
|
env,
|
|
1757
2788
|
owned->data(),
|
|
1758
2789
|
owned->size(),
|
|
@@ -1789,6 +2820,21 @@ void StreamMessageToJs(Napi::Env env, Napi::Function callback, StreamMessage *me
|
|
|
1789
2820
|
}
|
|
1790
2821
|
}
|
|
1791
2822
|
|
|
2823
|
+
void FileCompletionToJs(
|
|
2824
|
+
Napi::Env env,
|
|
2825
|
+
Napi::Function callback,
|
|
2826
|
+
FileCompletionMessage *message)
|
|
2827
|
+
{
|
|
2828
|
+
std::unique_ptr<FileCompletionMessage> owned(message);
|
|
2829
|
+
if (message->kind == FileCompletionMessage::Kind::Barrier) {
|
|
2830
|
+
message->acknowledgement->Acknowledge();
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
if (env != nullptr && callback) {
|
|
2834
|
+
callback.Call({Napi::Number::New(env, message->entryId)});
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
|
|
1792
2838
|
class BoundedAsyncWorker : public Napi::AsyncWorker {
|
|
1793
2839
|
public:
|
|
1794
2840
|
BoundedAsyncWorker(Napi::Env env, Napi::Promise::Deferred deferred)
|
|
@@ -1815,11 +2861,13 @@ public:
|
|
|
1815
2861
|
Napi::Promise::Deferred deferred,
|
|
1816
2862
|
std::string path,
|
|
1817
2863
|
std::optional<std::string> password,
|
|
1818
|
-
std::string enginePath
|
|
2864
|
+
std::string enginePath,
|
|
2865
|
+
std::uint32_t deflateBufferSize)
|
|
1819
2866
|
: BoundedAsyncWorker(env, deferred),
|
|
1820
2867
|
path_(std::move(path)),
|
|
1821
2868
|
password_(std::move(password)),
|
|
1822
|
-
enginePath_(std::move(enginePath))
|
|
2869
|
+
enginePath_(std::move(enginePath)),
|
|
2870
|
+
deflateBufferSize_(deflateBufferSize) {}
|
|
1823
2871
|
|
|
1824
2872
|
~OpenWorker() override
|
|
1825
2873
|
{
|
|
@@ -1832,7 +2880,7 @@ public:
|
|
|
1832
2880
|
{
|
|
1833
2881
|
try {
|
|
1834
2882
|
archive_ = std::make_shared<ArchiveEngine>();
|
|
1835
|
-
archive_->Open(path_, std::move(password_), enginePath_);
|
|
2883
|
+
archive_->Open(path_, std::move(password_), enginePath_, deflateBufferSize_);
|
|
1836
2884
|
} catch (const std::exception& error) {
|
|
1837
2885
|
SetError(error.what());
|
|
1838
2886
|
} catch (...) {
|
|
@@ -1854,6 +2902,7 @@ private:
|
|
|
1854
2902
|
std::string path_;
|
|
1855
2903
|
std::optional<std::string> password_;
|
|
1856
2904
|
std::string enginePath_;
|
|
2905
|
+
std::uint32_t deflateBufferSize_;
|
|
1857
2906
|
std::shared_ptr<ArchiveEngine> archive_;
|
|
1858
2907
|
};
|
|
1859
2908
|
|
|
@@ -1897,15 +2946,14 @@ public:
|
|
|
1897
2946
|
Napi::Promise::Deferred deferred,
|
|
1898
2947
|
std::shared_ptr<ArchiveEngine> archive,
|
|
1899
2948
|
std::vector<std::uint32_t> ids,
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
std::uint64_t length)
|
|
2949
|
+
std::vector<ReadRange> ranges,
|
|
2950
|
+
bool singleOutput)
|
|
1903
2951
|
: deferred_(deferred),
|
|
1904
2952
|
archive_(std::move(archive)),
|
|
1905
2953
|
ids_(std::move(ids)),
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
2954
|
+
ranges_(std::move(ranges)),
|
|
2955
|
+
singleOutput_(singleOutput),
|
|
2956
|
+
profileEnabled_(std::getenv("ZIP_NATIVE_PROFILE_READ") != nullptr) {}
|
|
1909
2957
|
|
|
1910
2958
|
~ExtractWorker() override
|
|
1911
2959
|
{
|
|
@@ -1914,23 +2962,58 @@ public:
|
|
|
1914
2962
|
|
|
1915
2963
|
void Execute() noexcept override
|
|
1916
2964
|
{
|
|
2965
|
+
std::chrono::steady_clock::time_point started;
|
|
2966
|
+
if (profileEnabled_) {
|
|
2967
|
+
started = std::chrono::steady_clock::now();
|
|
2968
|
+
}
|
|
1917
2969
|
try {
|
|
1918
|
-
outputs_ = archive_->Extract(
|
|
2970
|
+
outputs_ = archive_->Extract(
|
|
2971
|
+
ids_,
|
|
2972
|
+
ranges_,
|
|
2973
|
+
outputReservation_,
|
|
2974
|
+
profileEnabled_ ? &profile_ : nullptr);
|
|
1919
2975
|
} catch (const std::exception& error) {
|
|
1920
2976
|
error_ = error.what();
|
|
1921
2977
|
} catch (...) {
|
|
1922
2978
|
error_ = "Unknown native extraction failure";
|
|
1923
2979
|
}
|
|
2980
|
+
if (profileEnabled_) {
|
|
2981
|
+
executionMs_ = std::chrono::duration<double, std::milli>(
|
|
2982
|
+
std::chrono::steady_clock::now() - started).count();
|
|
2983
|
+
}
|
|
1924
2984
|
}
|
|
1925
2985
|
|
|
1926
2986
|
void Complete(Napi::Env env) override
|
|
1927
2987
|
{
|
|
2988
|
+
if (profileEnabled_) {
|
|
2989
|
+
std::fprintf(
|
|
2990
|
+
stderr,
|
|
2991
|
+
"[7zip-native profile] worker=%.3f ms, validation=%.3f, reservation=%.3f, "
|
|
2992
|
+
"requestSetup=%.3f, contextAcquire=%.3f, solidCheck=%.3f, callbackSetup=%.3f, "
|
|
2993
|
+
"sevenZipExtract=%.3f (%u calls; perItem=%d; callMin=%.3f; callMax=%.3f), "
|
|
2994
|
+
"resultProcessing=%.3f, entries=%zu\n",
|
|
2995
|
+
executionMs_,
|
|
2996
|
+
profile_.validationMs,
|
|
2997
|
+
profile_.reservationMs,
|
|
2998
|
+
profile_.requestSetupMs,
|
|
2999
|
+
profile_.contextAcquireMs,
|
|
3000
|
+
profile_.solidCheckMs,
|
|
3001
|
+
profile_.callbackSetupMs,
|
|
3002
|
+
profile_.sevenZipExtractMs,
|
|
3003
|
+
profile_.sevenZipCalls,
|
|
3004
|
+
profile_.abortPerItem,
|
|
3005
|
+
profile_.minSevenZipCallMs,
|
|
3006
|
+
profile_.maxSevenZipCallMs,
|
|
3007
|
+
profile_.resultProcessingMs,
|
|
3008
|
+
ids_.size());
|
|
3009
|
+
std::fflush(stderr);
|
|
3010
|
+
}
|
|
1928
3011
|
try {
|
|
1929
3012
|
if (!error_.empty()) {
|
|
1930
3013
|
deferred_.Reject(Napi::Error::New(env, error_).Value());
|
|
1931
3014
|
return;
|
|
1932
3015
|
}
|
|
1933
|
-
if (
|
|
3016
|
+
if (singleOutput_) {
|
|
1934
3017
|
deferred_.Resolve(TakeBuffer(env, std::move(outputs_.front())));
|
|
1935
3018
|
return;
|
|
1936
3019
|
}
|
|
@@ -1951,9 +3034,11 @@ private:
|
|
|
1951
3034
|
OutputReservation outputReservation_;
|
|
1952
3035
|
std::shared_ptr<ArchiveEngine> archive_;
|
|
1953
3036
|
std::vector<std::uint32_t> ids_;
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
3037
|
+
std::vector<ReadRange> ranges_;
|
|
3038
|
+
bool singleOutput_;
|
|
3039
|
+
bool profileEnabled_;
|
|
3040
|
+
double executionMs_ = 0;
|
|
3041
|
+
ExtractionProfile profile_;
|
|
1957
3042
|
std::vector<std::vector<std::uint8_t>> outputs_;
|
|
1958
3043
|
std::string error_;
|
|
1959
3044
|
};
|
|
@@ -2078,6 +3163,65 @@ private:
|
|
|
2078
3163
|
bool completed_ = false;
|
|
2079
3164
|
};
|
|
2080
3165
|
|
|
3166
|
+
class ExtractToFilesWorker final : public ExtractionTask {
|
|
3167
|
+
public:
|
|
3168
|
+
ExtractToFilesWorker(
|
|
3169
|
+
Napi::Promise::Deferred deferred,
|
|
3170
|
+
std::shared_ptr<ArchiveEngine> archive,
|
|
3171
|
+
std::vector<NativeFileOutputSpec> outputs,
|
|
3172
|
+
std::shared_ptr<NativeFileTaskState> task)
|
|
3173
|
+
: deferred_(deferred),
|
|
3174
|
+
archive_(std::move(archive)),
|
|
3175
|
+
outputs_(std::move(outputs)),
|
|
3176
|
+
task_(std::move(task)) {}
|
|
3177
|
+
|
|
3178
|
+
~ExtractToFilesWorker() override
|
|
3179
|
+
{
|
|
3180
|
+
RemoveFileTask(task_->id);
|
|
3181
|
+
task_->ReleaseCallback();
|
|
3182
|
+
g_pendingOperations.fetch_sub(1, std::memory_order_acq_rel);
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
void Execute() noexcept override
|
|
3186
|
+
{
|
|
3187
|
+
AsyncFileWriter writer(task_);
|
|
3188
|
+
try {
|
|
3189
|
+
writer.Start();
|
|
3190
|
+
archive_->ExtractToFiles(outputs_, task_, writer);
|
|
3191
|
+
} catch (const std::exception& error) {
|
|
3192
|
+
error_ = error.what();
|
|
3193
|
+
} catch (...) {
|
|
3194
|
+
error_ = "Unknown native file extraction failure";
|
|
3195
|
+
}
|
|
3196
|
+
writer.StopAndJoin();
|
|
3197
|
+
if (error_.empty()) {
|
|
3198
|
+
error_ = task_->Failure();
|
|
3199
|
+
if (error_.empty() && task_->IsCancelled()) {
|
|
3200
|
+
error_ = "Native folder extraction was cancelled";
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
if (!task_->WaitForFileCompletions() && error_.empty()) {
|
|
3204
|
+
error_ = "Unable to deliver native file completion events";
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
void Complete(Napi::Env env) override
|
|
3209
|
+
{
|
|
3210
|
+
if (!error_.empty()) {
|
|
3211
|
+
deferred_.Reject(Napi::Error::New(env, error_).Value());
|
|
3212
|
+
return;
|
|
3213
|
+
}
|
|
3214
|
+
deferred_.Resolve(env.Undefined());
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
private:
|
|
3218
|
+
Napi::Promise::Deferred deferred_;
|
|
3219
|
+
std::shared_ptr<ArchiveEngine> archive_;
|
|
3220
|
+
std::vector<NativeFileOutputSpec> outputs_;
|
|
3221
|
+
std::shared_ptr<NativeFileTaskState> task_;
|
|
3222
|
+
std::string error_;
|
|
3223
|
+
};
|
|
3224
|
+
|
|
2081
3225
|
class ExtractionPool {
|
|
2082
3226
|
public:
|
|
2083
3227
|
void Initialize(Napi::Env env)
|
|
@@ -2244,9 +3388,19 @@ private:
|
|
|
2244
3388
|
Napi::Value Open(const Napi::CallbackInfo& info)
|
|
2245
3389
|
{
|
|
2246
3390
|
const auto env = info.Env();
|
|
2247
|
-
if (info.Length() !=
|
|
2248
|
-
|| (!info[1].IsUndefined() && !info[1].IsString())
|
|
2249
|
-
|
|
3391
|
+
if (info.Length() != 4 || !info[0].IsString()
|
|
3392
|
+
|| (!info[1].IsUndefined() && !info[1].IsString())
|
|
3393
|
+
|| !info[2].IsString() || !info[3].IsNumber()) {
|
|
3394
|
+
throw Napi::TypeError::New(
|
|
3395
|
+
env, "open(path, password?, enginePath, deflateBufferSize) expects a path, optional password, engine path, and buffer size");
|
|
3396
|
+
}
|
|
3397
|
+
const double deflateBufferSizeValue = info[3].As<Napi::Number>().DoubleValue();
|
|
3398
|
+
if (!std::isfinite(deflateBufferSizeValue)
|
|
3399
|
+
|| std::floor(deflateBufferSizeValue) != deflateBufferSizeValue
|
|
3400
|
+
|| deflateBufferSizeValue < kMinDeflateBufferSize
|
|
3401
|
+
|| deflateBufferSizeValue > kMaxDeflateBufferSize) {
|
|
3402
|
+
throw Napi::RangeError::New(
|
|
3403
|
+
env, "Deflate buffer size must be an integer between 4096 and 2097152 bytes");
|
|
2250
3404
|
}
|
|
2251
3405
|
std::optional<std::string> password;
|
|
2252
3406
|
if (info[1].IsString()) {
|
|
@@ -2264,7 +3418,8 @@ Napi::Value Open(const Napi::CallbackInfo& info)
|
|
|
2264
3418
|
deferred,
|
|
2265
3419
|
info[0].As<Napi::String>().Utf8Value(),
|
|
2266
3420
|
std::move(password),
|
|
2267
|
-
info[2].As<Napi::String>().Utf8Value()
|
|
3421
|
+
info[2].As<Napi::String>().Utf8Value(),
|
|
3422
|
+
static_cast<std::uint32_t>(deflateBufferSizeValue));
|
|
2268
3423
|
worker->Queue();
|
|
2269
3424
|
return deferred.Promise();
|
|
2270
3425
|
}
|
|
@@ -2300,13 +3455,19 @@ Napi::Value CreateArchive(const Napi::CallbackInfo& info)
|
|
|
2300
3455
|
const auto item = value.As<Napi::Object>();
|
|
2301
3456
|
const auto sourcePath = item.Get("sourcePath");
|
|
2302
3457
|
const auto archivePath = item.Get("path");
|
|
2303
|
-
|
|
3458
|
+
const auto mtimeMs = item.Get("mtimeMs");
|
|
3459
|
+
if (!sourcePath.IsString() || !archivePath.IsString() || !mtimeMs.IsNumber()) {
|
|
2304
3460
|
throw Napi::TypeError::New(
|
|
2305
|
-
env, "Archive creation entries require string sourcePath and
|
|
3461
|
+
env, "Archive creation entries require string sourcePath, path, and numeric mtimeMs values");
|
|
3462
|
+
}
|
|
3463
|
+
const double sourceMtimeMs = mtimeMs.As<Napi::Number>().DoubleValue();
|
|
3464
|
+
if (!std::isfinite(sourceMtimeMs)) {
|
|
3465
|
+
throw Napi::TypeError::New(env, "Archive creation entry mtimeMs must be finite");
|
|
2306
3466
|
}
|
|
2307
3467
|
entries.push_back({
|
|
2308
3468
|
sourcePath.As<Napi::String>().Utf8Value(),
|
|
2309
3469
|
archivePath.As<Napi::String>().Utf8Value(),
|
|
3470
|
+
sourceMtimeMs,
|
|
2310
3471
|
});
|
|
2311
3472
|
}
|
|
2312
3473
|
if (!ReserveOperation()) {
|
|
@@ -2368,9 +3529,8 @@ Napi::Value QueueExtraction(
|
|
|
2368
3529
|
Napi::Promise::Deferred deferred,
|
|
2369
3530
|
std::shared_ptr<ArchiveEngine> archive,
|
|
2370
3531
|
std::vector<std::uint32_t> ids,
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
std::uint64_t length)
|
|
3532
|
+
std::vector<ReadRange> ranges,
|
|
3533
|
+
bool singleOutput)
|
|
2374
3534
|
{
|
|
2375
3535
|
if (!ReserveOperation()) {
|
|
2376
3536
|
throw Napi::Error::New(env, "Native operation queue is full");
|
|
@@ -2381,7 +3541,7 @@ Napi::Value QueueExtraction(
|
|
|
2381
3541
|
throw std::runtime_error("Native extraction pool is not initialized");
|
|
2382
3542
|
}
|
|
2383
3543
|
worker = new ExtractWorker(
|
|
2384
|
-
deferred, std::move(archive), std::move(ids),
|
|
3544
|
+
deferred, std::move(archive), std::move(ids), std::move(ranges), singleOutput);
|
|
2385
3545
|
g_extractionPool->Queue(env, worker);
|
|
2386
3546
|
} catch (const std::exception& error) {
|
|
2387
3547
|
if (worker != nullptr) {
|
|
@@ -2424,7 +3584,7 @@ Napi::Value Extract(const Napi::CallbackInfo& info)
|
|
|
2424
3584
|
}
|
|
2425
3585
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
2426
3586
|
return QueueExtraction(
|
|
2427
|
-
env, deferred, std::move(archive), std::move(ids),
|
|
3587
|
+
env, deferred, std::move(archive), std::move(ids), {}, false);
|
|
2428
3588
|
}
|
|
2429
3589
|
|
|
2430
3590
|
Napi::Value StartStream(const Napi::CallbackInfo& info)
|
|
@@ -2491,6 +3651,128 @@ Napi::Value StartStream(const Napi::CallbackInfo& info)
|
|
|
2491
3651
|
}
|
|
2492
3652
|
}
|
|
2493
3653
|
|
|
3654
|
+
Napi::Value StartExtractToFiles(const Napi::CallbackInfo& info)
|
|
3655
|
+
{
|
|
3656
|
+
const auto env = info.Env();
|
|
3657
|
+
if (info.Length() < 2
|
|
3658
|
+
|| info.Length() > 3
|
|
3659
|
+
|| !info[1].IsArray()
|
|
3660
|
+
|| (info.Length() == 3 && !info[2].IsUndefined() && !info[2].IsFunction())) {
|
|
3661
|
+
throw Napi::TypeError::New(
|
|
3662
|
+
env, "startExtractToFiles(handle, files, fileCompleteCallback?) expects a handle, file list, and an optional file-completion callback");
|
|
3663
|
+
}
|
|
3664
|
+
const auto handle = GetHandle(info[0]);
|
|
3665
|
+
auto archive = FindArchive(handle);
|
|
3666
|
+
if (!archive) {
|
|
3667
|
+
throw Napi::Error::New(env, "Archive is closed");
|
|
3668
|
+
}
|
|
3669
|
+
const auto inputFiles = info[1].As<Napi::Array>();
|
|
3670
|
+
if (inputFiles.Length() > kMaxEntries) {
|
|
3671
|
+
throw Napi::RangeError::New(env, "Folder extraction batch exceeds the native entry limit");
|
|
3672
|
+
}
|
|
3673
|
+
std::vector<NativeFileOutputSpec> outputs;
|
|
3674
|
+
outputs.reserve(inputFiles.Length());
|
|
3675
|
+
for (std::uint32_t index = 0; index < inputFiles.Length(); ++index) {
|
|
3676
|
+
const auto value = inputFiles.Get(index);
|
|
3677
|
+
if (!value.IsObject() || value.IsNull()) {
|
|
3678
|
+
throw Napi::TypeError::New(env, "Folder extraction files must be objects");
|
|
3679
|
+
}
|
|
3680
|
+
const auto file = value.As<Napi::Object>();
|
|
3681
|
+
std::uint64_t id = 0;
|
|
3682
|
+
const auto path = file.Get("path");
|
|
3683
|
+
const auto overwrite = file.Get("overwrite");
|
|
3684
|
+
if (!ReadUnsigned(file.Get("id"), id)
|
|
3685
|
+
|| id > std::numeric_limits<std::uint32_t>::max()
|
|
3686
|
+
|| !path.IsString()
|
|
3687
|
+
|| !overwrite.IsBoolean()) {
|
|
3688
|
+
throw Napi::TypeError::New(
|
|
3689
|
+
env, "Folder extraction files require a valid entry ID, output path, and overwrite flag");
|
|
3690
|
+
}
|
|
3691
|
+
outputs.push_back({
|
|
3692
|
+
static_cast<std::uint32_t>(id),
|
|
3693
|
+
path.As<Napi::String>().Utf8Value(),
|
|
3694
|
+
overwrite.As<Napi::Boolean>().Value(),
|
|
3695
|
+
});
|
|
3696
|
+
}
|
|
3697
|
+
if (g_extractionPool == nullptr) {
|
|
3698
|
+
throw Napi::Error::New(env, "Native extraction pool is not initialized");
|
|
3699
|
+
}
|
|
3700
|
+
if (!ReserveOperation()) {
|
|
3701
|
+
throw Napi::Error::New(env, "Native operation queue is full");
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
std::shared_ptr<NativeFileTaskState> task;
|
|
3705
|
+
ExtractToFilesWorker *worker = nullptr;
|
|
3706
|
+
std::uint64_t taskId = 0;
|
|
3707
|
+
try {
|
|
3708
|
+
task = std::make_shared<NativeFileTaskState>();
|
|
3709
|
+
if (info.Length() == 3 && info[2].IsFunction()) {
|
|
3710
|
+
task->fileCompletionCallback = Napi::ThreadSafeFunction::New(
|
|
3711
|
+
env,
|
|
3712
|
+
info[2].As<Napi::Function>(),
|
|
3713
|
+
"7zip-native folder file completion",
|
|
3714
|
+
1,
|
|
3715
|
+
1);
|
|
3716
|
+
task->hasFileCompletionCallback_ = true;
|
|
3717
|
+
}
|
|
3718
|
+
taskId = RegisterFileTask(task);
|
|
3719
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
3720
|
+
auto result = Napi::Object::New(env);
|
|
3721
|
+
result.Set("taskId", Napi::BigInt::New(env, taskId));
|
|
3722
|
+
result.Set("promise", deferred.Promise());
|
|
3723
|
+
worker = new ExtractToFilesWorker(
|
|
3724
|
+
deferred,
|
|
3725
|
+
std::move(archive),
|
|
3726
|
+
std::move(outputs),
|
|
3727
|
+
task);
|
|
3728
|
+
g_extractionPool->Queue(env, worker);
|
|
3729
|
+
worker = nullptr;
|
|
3730
|
+
return result;
|
|
3731
|
+
} catch (const std::exception& error) {
|
|
3732
|
+
if (worker != nullptr) {
|
|
3733
|
+
delete worker;
|
|
3734
|
+
} else {
|
|
3735
|
+
if (taskId != 0) {
|
|
3736
|
+
RemoveFileTask(taskId);
|
|
3737
|
+
}
|
|
3738
|
+
if (task) {
|
|
3739
|
+
task->Cancel();
|
|
3740
|
+
task->ReleaseCallback();
|
|
3741
|
+
}
|
|
3742
|
+
g_pendingOperations.fetch_sub(1, std::memory_order_acq_rel);
|
|
3743
|
+
}
|
|
3744
|
+
throw Napi::Error::New(env, error.what());
|
|
3745
|
+
} catch (...) {
|
|
3746
|
+
if (worker != nullptr) {
|
|
3747
|
+
delete worker;
|
|
3748
|
+
} else {
|
|
3749
|
+
if (taskId != 0) {
|
|
3750
|
+
RemoveFileTask(taskId);
|
|
3751
|
+
}
|
|
3752
|
+
if (task) {
|
|
3753
|
+
task->Cancel();
|
|
3754
|
+
task->ReleaseCallback();
|
|
3755
|
+
}
|
|
3756
|
+
g_pendingOperations.fetch_sub(1, std::memory_order_acq_rel);
|
|
3757
|
+
}
|
|
3758
|
+
throw Napi::Error::New(env, "Unable to queue native folder extraction");
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3762
|
+
Napi::Value CancelExtractToFiles(const Napi::CallbackInfo& info)
|
|
3763
|
+
{
|
|
3764
|
+
const auto env = info.Env();
|
|
3765
|
+
std::uint64_t taskId = 0;
|
|
3766
|
+
if (info.Length() != 1 || !ReadUnsigned(info[0], taskId) || taskId == 0) {
|
|
3767
|
+
throw Napi::TypeError::New(
|
|
3768
|
+
env, "cancelExtractToFiles(taskId) expects a positive task ID");
|
|
3769
|
+
}
|
|
3770
|
+
if (const auto task = FindFileTask(taskId)) {
|
|
3771
|
+
task->Cancel();
|
|
3772
|
+
}
|
|
3773
|
+
return env.Undefined();
|
|
3774
|
+
}
|
|
3775
|
+
|
|
2494
3776
|
Napi::Value ResumeStream(const Napi::CallbackInfo& info)
|
|
2495
3777
|
{
|
|
2496
3778
|
const auto env = info.Env();
|
|
@@ -2544,9 +3826,52 @@ Napi::Value Read(const Napi::CallbackInfo& info)
|
|
|
2544
3826
|
deferred,
|
|
2545
3827
|
std::move(archive),
|
|
2546
3828
|
{static_cast<std::uint32_t>(id)},
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
3829
|
+
{{offset, length}},
|
|
3830
|
+
true);
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
Napi::Value ReadMany(const Napi::CallbackInfo& info)
|
|
3834
|
+
{
|
|
3835
|
+
const auto env = info.Env();
|
|
3836
|
+
if (info.Length() != 2 || !info[1].IsArray()) {
|
|
3837
|
+
throw Napi::TypeError::New(env, "readMany(handle, requests) expects an array of read requests");
|
|
3838
|
+
}
|
|
3839
|
+
const auto handle = GetHandle(info[0]);
|
|
3840
|
+
auto archive = FindArchive(handle);
|
|
3841
|
+
if (!archive) {
|
|
3842
|
+
throw Napi::Error::New(env, "Archive is closed");
|
|
3843
|
+
}
|
|
3844
|
+
const auto inputRequests = info[1].As<Napi::Array>();
|
|
3845
|
+
if (inputRequests.Length() > kMaxEntries) {
|
|
3846
|
+
throw Napi::RangeError::New(env, "Batch read exceeds the native entry limit");
|
|
3847
|
+
}
|
|
3848
|
+
std::vector<std::uint32_t> ids;
|
|
3849
|
+
std::vector<ReadRange> ranges;
|
|
3850
|
+
ids.reserve(inputRequests.Length());
|
|
3851
|
+
ranges.reserve(inputRequests.Length());
|
|
3852
|
+
for (std::uint32_t i = 0; i < inputRequests.Length(); ++i) {
|
|
3853
|
+
const auto value = inputRequests.Get(i);
|
|
3854
|
+
if (!value.IsObject() || value.IsNull()) {
|
|
3855
|
+
throw Napi::TypeError::New(env, "Batch read requests must be objects");
|
|
3856
|
+
}
|
|
3857
|
+
const auto request = value.As<Napi::Object>();
|
|
3858
|
+
std::uint64_t id = 0;
|
|
3859
|
+
std::uint64_t offset = 0;
|
|
3860
|
+
std::uint64_t length = 0;
|
|
3861
|
+
if (!ReadUnsigned(request.Get("id"), id) || id > std::numeric_limits<std::uint32_t>::max()
|
|
3862
|
+
|| !ReadUnsigned(request.Get("offset"), offset) || !ReadUnsigned(request.Get("length"), length)) {
|
|
3863
|
+
throw Napi::TypeError::New(
|
|
3864
|
+
env, "Batch read requests require a valid entry ID, offset, and length");
|
|
3865
|
+
}
|
|
3866
|
+
if (length > kMaxReadBytes || offset > std::numeric_limits<std::uint64_t>::max() - length) {
|
|
3867
|
+
throw Napi::RangeError::New(env, "Requested range exceeds the native read limit");
|
|
3868
|
+
}
|
|
3869
|
+
ids.push_back(static_cast<std::uint32_t>(id));
|
|
3870
|
+
ranges.push_back({offset, length});
|
|
3871
|
+
}
|
|
3872
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
3873
|
+
return QueueExtraction(
|
|
3874
|
+
env, deferred, std::move(archive), std::move(ids), std::move(ranges), false);
|
|
2550
3875
|
}
|
|
2551
3876
|
|
|
2552
3877
|
Napi::Value Concurrency(const Napi::CallbackInfo& info)
|
|
@@ -2610,7 +3935,10 @@ Napi::Object Init(Napi::Env env, Napi::Object exports)
|
|
|
2610
3935
|
exports.Set("create", Napi::Function::New(env, CreateArchive));
|
|
2611
3936
|
exports.Set("entries", Napi::Function::New(env, Entries));
|
|
2612
3937
|
exports.Set("extract", Napi::Function::New(env, Extract));
|
|
3938
|
+
exports.Set("readMany", Napi::Function::New(env, ReadMany));
|
|
2613
3939
|
exports.Set("startStream", Napi::Function::New(env, StartStream));
|
|
3940
|
+
exports.Set("startExtractToFiles", Napi::Function::New(env, StartExtractToFiles));
|
|
3941
|
+
exports.Set("cancelExtractToFiles", Napi::Function::New(env, CancelExtractToFiles));
|
|
2614
3942
|
exports.Set("resumeStream", Napi::Function::New(env, ResumeStream));
|
|
2615
3943
|
exports.Set("cancelStream", Napi::Function::New(env, CancelStream));
|
|
2616
3944
|
exports.Set("read", Napi::Function::New(env, Read));
|