Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ios/flutter_7zip.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ A new Flutter FFI plugin project.
s.platform = :ios, '12.0'

# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++20' }
s.swift_version = '5.0'
Comment on lines 23 to 27
end
103 changes: 78 additions & 25 deletions lib/flutter_7zip.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,42 @@ final Flutter7zipBindings _bindings = Flutter7zipBindings(_dylib);
final _nativeFreeDataFunc =
_dylib.lookup<NativeFunction<Void Function(Pointer<Void>)>>('freeReadData');

/// Timing statistics for a single extraction operation.
class FileExtractStat {
/// Index of the file in the archive.
final int fileIndex;

/// Whether this entry was a directory.
final bool isDirectory;

/// Elapsed milliseconds for this single file extraction.
final int elapsedMs;

const FileExtractStat(this.fileIndex, this.isDirectory, this.elapsedMs);
}

/// Wrapper around [SZArchive.extract] return value.
class ArchiveExtractResult {
/// Total elapsed milliseconds for the entire extraction.
final int totalElapsedMs;

/// Number of files extracted.
final int totalFiles;

const ArchiveExtractResult(this.totalElapsedMs, this.totalFiles);

@override
String toString() => '${totalFiles}files/${totalElapsedMs}ms';
}

class SZArchive {
final Pointer<Void> _archive;
final _pointers = <Pointer>[];
final Stopwatch _stopwatch = Stopwatch();
final List<FileExtractStat> _fileStats = [];

SZArchive._(this._archive);

final _pointers = <Pointer>[];

/// Dispose the archive and free all resources.
void dispose() {
_bindings.closeArchive(_archive);
Expand All @@ -43,6 +72,18 @@ class SZArchive {
}
}

/// Total elapsed milliseconds spent extracting files from this archive.
int get elapsedMs => _stopwatch.elapsedMilliseconds;

/// Per-file extraction statistics collected since opening.
List<FileExtractStat> get fileStats => List.unmodifiable(_fileStats);

/// Reset timing counters.
void resetTiming() {
_stopwatch.reset();
_fileStats.clear();
}

/// Get the number of files in the archive.
int get numFiles => _bindings.getArchiveFileCount(_archive);

Expand Down Expand Up @@ -107,9 +148,8 @@ class SZArchive {
/// Extract the file at the given [index] to a file at the given [path].
void extractToFile(int index, String path) {
var p = path.toNativeUtf8();
var file = File(path);
if (!file.existsSync()) {
file.createSync(recursive: true);
if (!File(path).parent.existsSync()) {
File(path).parent.createSync(recursive: true);
}
final status = _bindings.extractArchiveToFile(_archive, index, p.cast());
malloc.free(p);
Expand All @@ -118,6 +158,24 @@ class SZArchive {
}
}

/// Extract file by [index] to [outputDir], creating subdirs as needed.
/// Returns true if entry was a directory (already created).
/// Throws on error.
bool extractToDir(int index, String outputDir) {
_stopwatch.start();
final before = _stopwatch.elapsedMilliseconds;
var p = outputDir.toNativeUtf8();
final result = _bindings.extractFileToDir(_archive, index, p.cast());
malloc.free(p);
if (result < 0 || result > 1) {
_stopwatch.stop();
throw Exception('Failed to extract file index $index (code=$result)');
}
final after = _stopwatch.elapsedMilliseconds;
_fileStats.add(FileExtractStat(index, result == 1, after - before));
return result == 1;
Comment on lines +164 to +176
}

/// Open an archive at the given [path].
static SZArchive open(String path) {
var p = path.toNativeUtf8();
Expand All @@ -133,30 +191,29 @@ class SZArchive {
}

/// Extract the archive at the given [archivePath] to the given [outputPath].
///
/// The method is synchronous and will block the main thread.
static void extract(String archivePath, String outputPath) {
///
/// Returns [ArchiveExtractResult] with total timing and file count.
static ArchiveExtractResult extract(String archivePath, String outputPath) {
final sw = Stopwatch()..start();
var archive = open(archivePath);
for (var i = 0; i < archive.numFiles; i++) {
var file = archive.getFile(i);
var outPath = outputPath + Platform.pathSeparator + file.name;
if (file.isDirectory) {
Directory(outPath).createSync(recursive: true);
} else {
archive.extractToFile(i, outPath);
}
var total = archive.numFiles;
for (var i = 0; i < total; i++) {
archive.extractToDir(i, outputPath);
}
archive.dispose();
sw.stop();
return ArchiveExtractResult(sw.elapsedMilliseconds, total);
}

/// Extract the archive at the given [archivePath] to the given [outputPath] with [isolatesCount] isolates.
///
///
/// The method is asynchronous and will not block the main thread.
static Future<void> extractIsolates(
static Future<ArchiveExtractResult> extractIsolates(
String archivePath,
String outputPath,
int isolatesCount,
) async {
final sw = Stopwatch()..start();
var archive = open(archivePath);
var total = archive.numFiles;
var filesPerIsolate = total ~/ isolatesCount;
Expand All @@ -172,6 +229,8 @@ class SZArchive {
));
}
await Future.wait(futures);
sw.stop();
return ArchiveExtractResult(sw.elapsedMilliseconds, total);
}

static Future<void> _extractIsolate(
Expand All @@ -183,13 +242,7 @@ class SZArchive {
return Isolate.run(() {
var archive = open(archivePath);
for (var i = start; i < end; i++) {
var file = archive.getFile(i);
var outPath = outputPath + Platform.pathSeparator + file.name;
if (file.isDirectory) {
Directory(outPath).createSync(recursive: true);
} else {
archive.extractToFile(i, outPath);
}
archive.extractToDir(i, outputPath);
}
archive.dispose();
});
Expand Down
19 changes: 19 additions & 0 deletions lib/src/flutter_7zip_bindings_generated.dart
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,25 @@ class Flutter7zipBindings {
ffi.Pointer<ffi.Char>)>>('extractArchiveToFile');
late final _extractArchiveToFile = _extractArchiveToFilePtr.asFunction<
int Function(ffi.Pointer<ffi.Void>, int, ffi.Pointer<ffi.Char>)>();

int extractFileToDir(
ffi.Pointer<ffi.Void> archive,
int index,
ffi.Pointer<ffi.Char> outputDir,
) {
return _extractFileToDir(
archive,
index,
outputDir,
);
}

late final _extractFileToDirPtr = _lookup<
ffi.NativeFunction<
ffi.Int Function(ffi.Pointer<ffi.Void>, ffi.Uint32,
ffi.Pointer<ffi.Char>)>>('extractFileToDir');
late final _extractFileToDir = _extractFileToDirPtr.asFunction<
int Function(ffi.Pointer<ffi.Void>, int, ffi.Pointer<ffi.Char>)>();
}

final class ArchiveFile extends ffi.Struct {
Expand Down
2 changes: 1 addition & 1 deletion macos/flutter_7zip.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ A new Flutter FFI plugin project.
s.dependency 'FlutterMacOS'

s.platform = :osx, '10.11'
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++20' }
s.swift_version = '5.0'
Comment on lines 29 to 33
end
Loading