├── .gitignore ├── go.mod ├── sz ├── stats.go ├── objmap.go ├── libc7zip.h ├── glue.h ├── glue.c └── glue.go ├── .github └── workflows │ └── build.yml ├── go.sum ├── LICENSE ├── README.md └── cmd └── go7z └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.so 3 | *.dylib 4 | *.exe 5 | /go7z 6 | /out 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/itchio/sevenzip-go 2 | 3 | go 1.13 4 | 5 | require github.com/itchio/headway v0.0.0-20190702175331-a4c65c5306de 6 | -------------------------------------------------------------------------------- /sz/stats.go: -------------------------------------------------------------------------------- 1 | package sz 2 | 3 | type ReadStats struct { 4 | Reads []ReadOp 5 | } 6 | 7 | type ReadOp struct { 8 | Offset int64 9 | Size int64 10 | } 11 | 12 | func (rs *ReadStats) RecordRead(offset int64, size int64) { 13 | rs.Reads = append(rs.Reads, ReadOp{ 14 | Offset: offset, 15 | Size: size, 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macos-latest, windows-latest] 14 | runs-on: ${{ matrix.os }} 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version: '1.23' 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/itchio/headway v0.0.0-20190702175331-a4c65c5306de h1:RQW9xPqYtvjdHHRZR95XsaEA9B4URCuNHK78IuJcc+Y= 5 | github.com/itchio/headway v0.0.0-20190702175331-a4c65c5306de/go.mod h1:Iif+7HeesRB0PvTYf0gOIFX8lj0za0SUsWryENQYt1E= 6 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 7 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 8 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 9 | github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 h1:Ko2LQMrRU+Oy/+EDBwX7eZ2jp3C47eDBB8EIhKTun+I= 10 | github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 11 | -------------------------------------------------------------------------------- /sz/objmap.go: -------------------------------------------------------------------------------- 1 | package sz 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | ) 7 | 8 | var seed int64 = 1 9 | 10 | //========================== 11 | // OutStream 12 | //========================== 13 | 14 | var outStreams sync.Map 15 | 16 | func reserveOutStreamId(obj *OutStream) { 17 | obj.id = atomic.AddInt64(&seed, 1) 18 | outStreams.Store(obj.id, obj) 19 | } 20 | 21 | func freeOutStreamId(id int64) { 22 | outStreams.Delete(id) 23 | } 24 | 25 | //========================== 26 | // InStream 27 | //========================== 28 | 29 | var inStreams sync.Map 30 | 31 | func reserveInStreamId(obj *InStream) { 32 | obj.id = atomic.AddInt64(&seed, 1) 33 | inStreams.Store(obj.id, obj) 34 | } 35 | 36 | func freeInStreamId(id int64) { 37 | inStreams.Delete(id) 38 | } 39 | 40 | //========================== 41 | // ExtractCallback 42 | //========================== 43 | 44 | var extractCallbacks sync.Map 45 | 46 | func reserveExtractCallbackId(obj *ExtractCallback) { 47 | obj.id = atomic.AddInt64(&seed, 1) 48 | extractCallbacks.Store(obj.id, obj) 49 | } 50 | 51 | func freeExtractCallbackId(id int64) { 52 | extractCallbacks.Delete(id) 53 | } 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright (c) 2018 Amos Wenger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sevenzip-go 2 | 3 | [![Build](https://github.com/itchio/sevenzip-go/actions/workflows/build.yml/badge.svg)](https://github.com/itchio/sevenzip-go/actions/workflows/build.yml) 4 | [![GoDoc](https://godoc.org/github.com/itchio/sevenzip-go?status.svg)](https://godoc.org/github.com/itchio/sevenzip-go) 5 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/itchio/sevenzip-go/blob/master/LICENSE) 6 | [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 7 | 8 | 9 | Bindings to use 7-zip as a library from golang. 10 | 11 | ### Structure 12 | 13 | sevenzip-go needs two dynamic libraries to operate, and it expects them to be 14 | in the executable's folder. 15 | 16 | For example 17 | 18 | * On Windows, you'll need `foobar.exe`, `c7zip.dll`, and `7z.dll` in the same directory 19 | * On Linux, you'll need `foobar`, `libc7zip.so`, and `7z.so` in the same directory 20 | * On macOS, you'll need `foobar`, `libc7zip.dylib`, and `7z.so` in the same directory 21 | 22 | Note: the 7-zip library is called `7z.so` on macOS, that's not a typo. 23 | 24 | If it can't find it, it'll print messages to stderr (and return an error). 25 | 26 | #### Rationale 27 | 28 | sevenzip-go was made primarily to serve as a decompression engine for 29 | 30 | most of butler's functionality does not require 7-zip, and: 31 | 32 | * we want folks to be able to build butler easily, without having to build C/C++ projects manually 33 | * we want folks to be able to run their custom butler builds easily, without having to worry about missing 34 | dynamic libraries 35 | * we want to use `7z.dll` from the official 7-zip builds (it is a notorious pain to build, as it requires MSVC 2010) 36 | 37 | While the whole setup sounds crazy (especially considering the whole Go->cgo->C->C++->COM/C++ pipeline), 38 | it fits all those goals. 39 | 40 | ### Caveats 41 | 42 | Pay attention to the dynamic library requirement above: 43 | 44 | > Neither sevenzip-go nor lib7zip look for DLLs in the `PATH` or `LD_LIBRARY_PATH` or `DYLD_LIBRARY_PATH`, 45 | > they only look **in the executable's directory**. This is on purpose, so we don't accidentally load 46 | > an older version of 7-zip. 47 | 48 | The library allocates memory via C functions, so you should make sure to call `.Free()` on the 49 | various objects you get from sevenzip-go. 50 | 51 | Error handling is best-effort, but there's many moving pieces involved here. Some items of an archive 52 | may fail to extract, the errors can be retrieved with `extractCallback.Errors()` (which returns a slice of 53 | errors). 54 | 55 | ### Example 56 | 57 | The `./cmd/go7z` package 58 | 59 | ### Links 60 | 61 | * - a C wrapper for lib7zip, based on structs and function pointers 62 | * - a C++ wrapper for the 7-zip COM API, based on abstract base classes 63 | * - the official 7-zip distribution (Windows) 64 | * - a 7-zip port for Linux/macOS/etc. 65 | 66 | ### License 67 | 68 | sevenzip-go is released under the MIT license, see the `LICENSE` file. 69 | 70 | Other required components are distributed under the MPL 2.0, the LGPL 2.1, and 71 | other terms - see their own `LICENSE` or `COPYING` files. 72 | -------------------------------------------------------------------------------- /sz/libc7zip.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef LIBC7ZIP_H 3 | #define LIBC7ZIP_H 4 | 5 | #ifdef _MSC_VER 6 | #define MYEXPORT __declspec( dllexport ) 7 | #else 8 | #define MYEXPORT 9 | #endif 10 | 11 | #include 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif // __cplusplus 16 | 17 | struct lib; 18 | typedef struct lib lib; 19 | MYEXPORT lib *lib_new(); 20 | MYEXPORT int32_t lib_get_last_error(lib *l); 21 | MYEXPORT char *lib_get_version(lib *l); 22 | MYEXPORT void lib_free(lib *l); 23 | 24 | struct in_stream; 25 | typedef struct in_stream in_stream; 26 | 27 | struct out_stream; 28 | typedef struct out_stream out_stream; 29 | 30 | // InStream functions 31 | typedef int (*read_cb_t)(int64_t id, void *data, int64_t size, int64_t *processed_size); 32 | typedef int (*seek_cb_t)(int64_t id, int64_t offset, int32_t whence, int64_t *new_position); 33 | 34 | // SequentialOutStream functions 35 | typedef int (*write_cb_t)(int64_t id, const void *data, int64_t size, int64_t *processed_size); 36 | 37 | // ExtractCallback functions 38 | typedef void (*set_total_cb_t)(int64_t id, int64_t size); 39 | typedef void (*set_completed_cb_t)(int64_t id, int64_t complete_value); 40 | typedef out_stream *(*get_stream_cb_t)(int64_t id, int64_t index); 41 | typedef void (*set_operation_result_cb_t)(int64_t id, int32_t operation_result); 42 | 43 | typedef struct in_stream_def { 44 | int64_t id; 45 | seek_cb_t seek_cb; 46 | read_cb_t read_cb; 47 | char *ext; 48 | int64_t size; 49 | } in_stream_def; 50 | 51 | typedef struct out_stream_def { 52 | int64_t id; 53 | write_cb_t write_cb; 54 | } out_stream_def; 55 | 56 | MYEXPORT in_stream *in_stream_new(); 57 | MYEXPORT in_stream_def *in_stream_get_def(in_stream *is); 58 | MYEXPORT void in_stream_commit_def(in_stream *is); 59 | MYEXPORT void in_stream_free(in_stream *is); 60 | 61 | MYEXPORT out_stream *out_stream_new(); 62 | MYEXPORT out_stream_def *out_stream_get_def(out_stream *s); 63 | MYEXPORT void out_stream_free(out_stream *s); 64 | 65 | struct archive; 66 | typedef struct archive archive; 67 | MYEXPORT archive *archive_open(lib *l, in_stream *is, int32_t by_signature); 68 | MYEXPORT void archive_close(archive *a); 69 | MYEXPORT void archive_free(archive *a); 70 | MYEXPORT int64_t archive_get_item_count(archive *a); 71 | MYEXPORT char *archive_get_archive_format(archive *a); 72 | 73 | // copied from lib7zip.h so we don't have to include it 74 | enum property_index { 75 | PROP_INDEX_BEGIN, 76 | 77 | kpidPackSize = PROP_INDEX_BEGIN, //(Packed Size) 78 | kpidAttrib, //(Attributes) 79 | kpidCTime, //(Created) 80 | kpidATime, //(Accessed) 81 | kpidMTime, //(Modified) 82 | kpidSolid, //(Solid) 83 | kpidEncrypted, //(Encrypted) 84 | kpidUser, //(User) 85 | kpidGroup, //(Group) 86 | kpidComment, //(Comment) 87 | kpidPhySize, //(Physical Size) 88 | kpidHeadersSize, //(Headers Size) 89 | kpidChecksum, //(Checksum) 90 | kpidCharacts, //(Characteristics) 91 | kpidCreatorApp, //(Creator Application) 92 | kpidTotalSize, //(Total Size) 93 | kpidFreeSpace, //(Free Space) 94 | kpidClusterSize, //(Cluster Size) 95 | kpidVolumeName, //(Label) 96 | kpidPath, //(FullPath) 97 | kpidIsDir, //(IsDir) 98 | kpidSize, //(Uncompressed Size) 99 | kpidSymLink, //(Symbolic link destination) 100 | kpidPosixAttrib, //(POSIX Attributes) 101 | 102 | PROP_INDEX_END 103 | }; 104 | 105 | // copied from lib7zip.h so we don't have to include it 106 | enum error_code 107 | { 108 | LIB7ZIP_ErrorCode_Begin, 109 | 110 | LIB7ZIP_NO_ERROR = LIB7ZIP_ErrorCode_Begin, 111 | LIB7ZIP_UNKNOWN_ERROR, 112 | LIB7ZIP_NOT_INITIALIZE, 113 | LIB7ZIP_NEED_PASSWORD, 114 | LIB7ZIP_NOT_SUPPORTED_ARCHIVE, 115 | 116 | LIB7ZIP_ErrorCode_End 117 | }; 118 | 119 | struct item; 120 | typedef struct item item; 121 | MYEXPORT item *archive_get_item(archive *a, int64_t index); 122 | MYEXPORT int32_t item_get_archive_index(item *i); 123 | MYEXPORT char *item_get_string_property(item *i, int32_t property_index, int32_t *success); 124 | MYEXPORT void string_free(char *s); 125 | MYEXPORT uint64_t item_get_uint64_property(item *i, int32_t property_index, int32_t *success); 126 | MYEXPORT int32_t item_get_bool_property(item *i, int32_t property_index, int32_t *success); 127 | MYEXPORT void item_free(item *i); 128 | MYEXPORT int archive_extract_item(archive *a, item *i, out_stream *os); 129 | 130 | struct extract_callback; 131 | typedef struct extract_callback extract_callback; 132 | 133 | typedef struct extract_callback_def { 134 | int64_t id; 135 | set_total_cb_t set_total_cb; 136 | set_completed_cb_t set_completed_cb; 137 | get_stream_cb_t get_stream_cb; 138 | set_operation_result_cb_t set_operation_result_cb; 139 | } extract_callback_def; 140 | 141 | MYEXPORT extract_callback *extract_callback_new(); 142 | MYEXPORT extract_callback_def *extract_callback_get_def(extract_callback *ec); 143 | MYEXPORT void extract_callback_free(extract_callback *ec); 144 | 145 | MYEXPORT int archive_extract_several(archive *a, int64_t *indices, int32_t num_indices, extract_callback *ec); 146 | 147 | #ifdef __cplusplus 148 | } // extern "C" 149 | #endif // __cplusplus 150 | 151 | #endif // LIBC7ZIP_H -------------------------------------------------------------------------------- /sz/glue.h: -------------------------------------------------------------------------------- 1 | 2 | #include "libc7zip.h" 3 | 4 | #ifdef GLUE_IMPLEMENT 5 | #define GLUE 6 | #else 7 | #define GLUE extern 8 | #endif 9 | 10 | #define DECLARE(x) GLUE x##_t x##_; 11 | 12 | int libc7zip_initialize(char *lib_path); 13 | 14 | // lib_new 15 | typedef lib *(*lib_new_t)(); 16 | DECLARE(lib_new) 17 | lib *libc7zip_lib_new(); 18 | 19 | // lib_get_version 20 | typedef char *(*lib_get_version_t)(lib *l); 21 | DECLARE(lib_get_version) 22 | char *libc7zip_lib_get_version(lib *l); 23 | 24 | // lib_get_last_error 25 | typedef int32_t (*lib_get_last_error_t)(lib *l); 26 | DECLARE(lib_get_last_error) 27 | int32_t libc7zip_lib_get_last_error(lib *l); 28 | 29 | // lib_free 30 | typedef void (*lib_free_t)(lib *l); 31 | DECLARE(lib_free) 32 | void libc7zip_lib_free(lib *l); 33 | 34 | // in_stream_new 35 | typedef in_stream *(*in_stream_new_t)(); 36 | DECLARE(in_stream_new) 37 | in_stream *libc7zip_in_stream_new(); 38 | 39 | // in_stream_get_def 40 | typedef in_stream_def *(*in_stream_get_def_t)(in_stream *is); 41 | DECLARE(in_stream_get_def) 42 | in_stream_def *libc7zip_in_stream_get_def(in_stream *is); 43 | 44 | // in_stream_commit_def 45 | typedef void (*in_stream_commit_def_t)(in_stream *is); 46 | DECLARE(in_stream_commit_def) 47 | void libc7zip_in_stream_commit_def(in_stream *is); 48 | 49 | // in_stream_free 50 | typedef void (*in_stream_free_t)(in_stream *is); 51 | DECLARE(in_stream_free) 52 | void libc7zip_in_stream_free(in_stream *is); 53 | 54 | // out_stream_new 55 | typedef out_stream *(*out_stream_new_t)(); 56 | DECLARE(out_stream_new) 57 | out_stream *libc7zip_out_stream_new(); 58 | 59 | // out_stream_get_def 60 | typedef out_stream_def *(*out_stream_get_def_t)(out_stream *os); 61 | DECLARE(out_stream_get_def) 62 | out_stream_def *libc7zip_out_stream_get_def(out_stream *s); 63 | 64 | // out_stream_free 65 | typedef void (*out_stream_free_t)(out_stream *os); 66 | DECLARE(out_stream_free) 67 | void libc7zip_out_stream_free(out_stream *os); 68 | 69 | // archive_open 70 | typedef archive *(*archive_open_t)(lib *l, in_stream *s, int32_t by_signature); 71 | DECLARE(archive_open) 72 | archive *libc7zip_archive_open(lib *l, in_stream *s, int32_t by_signature); 73 | 74 | // archive_close 75 | typedef void (*archive_close_t)(archive *a); 76 | DECLARE(archive_close) 77 | void libc7zip_archive_close(archive *a); 78 | 79 | // archive_free 80 | typedef void (*archive_free_t)(archive *a); 81 | DECLARE(archive_free) 82 | void libc7zip_archive_free(archive *a); 83 | 84 | // archive_get_archive_format 85 | typedef char *(*archive_get_archive_format_t)(archive *a); 86 | DECLARE(archive_get_archive_format) 87 | char *libc7zip_archive_get_archive_format(archive *a); 88 | 89 | // archive_get_item_count 90 | typedef int64_t (*archive_get_item_count_t)(archive *a); 91 | DECLARE(archive_get_item_count) 92 | int64_t libc7zip_archive_get_item_count(archive *a); 93 | 94 | // archive_get_item 95 | typedef item *(*archive_get_item_t)(archive *a, int64_t index); 96 | DECLARE(archive_get_item) 97 | item *libc7zip_archive_get_item(archive *a, int64_t index); 98 | 99 | // item_get_archive_index 100 | typedef int32_t (*item_get_archive_index_t)(item *i); 101 | DECLARE(item_get_archive_index) 102 | int32_t libc7zip_item_get_archive_index(item *i); 103 | 104 | // item_get_string_property 105 | typedef char *(*item_get_string_property_t)(item *i, int32_t property_index, int32_t *success); 106 | DECLARE(item_get_string_property) 107 | char *libc7zip_item_get_string_property(item *i, int32_t property_index, int32_t *success); 108 | 109 | // string_free 110 | typedef void (*string_free_t)(char *s); 111 | DECLARE(string_free) 112 | void libc7zip_string_free(char *s); 113 | 114 | // item_get_uint64_property 115 | typedef uint64_t (*item_get_uint64_property_t)(item *i, int32_t property_index, int32_t *success); 116 | DECLARE(item_get_uint64_property) 117 | uint64_t libc7zip_item_get_uint64_property(item *i, int32_t property_index, int32_t *success); 118 | 119 | // item_get_bool_property 120 | typedef int32_t (*item_get_bool_property_t)(item *i, int32_t property_index, int32_t *success); 121 | DECLARE(item_get_bool_property) 122 | int32_t libc7zip_item_get_bool_property(item *i, int32_t property_index, int32_t *success); 123 | 124 | // item_free 125 | typedef void (*item_free_t)(item *i); 126 | DECLARE(item_free) 127 | void libc7zip_item_free(item *i); 128 | 129 | // archive_extract_item 130 | typedef int (*archive_extract_item_t)(archive *a, item *i, out_stream *os); 131 | DECLARE(archive_extract_item) 132 | int libc7zip_archive_extract_item(archive *a, item *i, out_stream *os); 133 | 134 | // extract_callback_new 135 | typedef extract_callback *(*extract_callback_new_t)(); 136 | DECLARE(extract_callback_new) 137 | extract_callback *libc7zip_extract_callback_new(); 138 | 139 | // extract_callback_get_def 140 | typedef extract_callback_def *(*extract_callback_get_def_t)(extract_callback *ec); 141 | DECLARE(extract_callback_get_def) 142 | extract_callback_def *libc7zip_extract_callback_get_def(extract_callback *ec); 143 | 144 | // extract_callback_free 145 | typedef void (*extract_callback_free_t)(extract_callback *ec); 146 | DECLARE(extract_callback_free) 147 | void libc7zip_extract_callback_free(extract_callback *ec); 148 | 149 | // archive_extract_several 150 | typedef int (*archive_extract_several_t)(archive *a, int64_t *indices, int32_t num_indices, extract_callback *ec); 151 | DECLARE(archive_extract_several) 152 | int libc7zip_archive_extract_several(archive *a, int64_t *indices, int32_t num_indices, extract_callback *ec); 153 | -------------------------------------------------------------------------------- /cmd/go7z/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "image" 6 | "image/color" 7 | "image/png" 8 | "io" 9 | "log" 10 | "math" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | 15 | "github.com/itchio/headway/united" 16 | "github.com/itchio/sevenzip-go/sz" 17 | ) 18 | 19 | type ecs struct { 20 | // muffin 21 | } 22 | 23 | func main() { 24 | lib, err := sz.NewLib() 25 | must(err) 26 | log.Printf("Initialized 7-zip %s...", lib.GetVersion()) 27 | defer lib.Free() 28 | 29 | args := os.Args[1:] 30 | 31 | if len(args) < 1 { 32 | log.Printf("Usage: go7z ARCHIVE") 33 | os.Exit(1) 34 | } 35 | 36 | inPath := args[0] 37 | ext := filepath.Ext(inPath) 38 | if ext != "" { 39 | ext = ext[1:] 40 | } 41 | log.Printf("ext = %s", ext) 42 | 43 | f, err := os.Open(inPath) 44 | must(err) 45 | 46 | stats, err := f.Stat() 47 | must(err) 48 | 49 | is, err := sz.NewInStream(f, ext, stats.Size()) 50 | must(err) 51 | log.Printf("Created input stream (%s, %d bytes)...", inPath, stats.Size()) 52 | 53 | is.Stats = &sz.ReadStats{} 54 | 55 | a, err := lib.OpenArchive(is, false) 56 | if err != nil { 57 | log.Printf("Could not open archive by ext, trying by signature") 58 | 59 | _, err = is.Seek(0, io.SeekStart) 60 | must(err) 61 | 62 | a, err = lib.OpenArchive(is, true) 63 | } 64 | must(err) 65 | 66 | log.Printf("Opened archive: format is (%s)", a.GetArchiveFormat()) 67 | 68 | itemCount, err := a.GetItemCount() 69 | must(err) 70 | log.Printf("Archive has %d items", itemCount) 71 | 72 | ec, err := sz.NewExtractCallback(&ecs{}) 73 | must(err) 74 | defer ec.Free() 75 | 76 | var indices = make([]int64, itemCount) 77 | for i := 0; i < int(itemCount); i++ { 78 | indices[i] = int64(i) 79 | } 80 | middle := itemCount / 2 81 | 82 | log.Printf("Doing first half...") 83 | err = a.ExtractSeveral(indices[0:middle], ec) 84 | must(err) 85 | 86 | for i := 0; i < 15; i++ { 87 | is.Stats.RecordRead(0, 0) 88 | } 89 | 90 | log.Printf("Doing second half...") 91 | err = a.ExtractSeveral(indices[middle:], ec) 92 | must(err) 93 | 94 | errs := ec.Errors() 95 | if len(errs) > 0 { 96 | log.Printf("There were %d errors during extraction:", len(errs)) 97 | for _, err := range errs { 98 | log.Printf("- %s", err.Error()) 99 | } 100 | } 101 | 102 | width := len(is.Stats.Reads) 103 | height := 800 104 | log.Printf("Making %dx%d image", width, height) 105 | 106 | rect := image.Rect(0, 0, width, height) 107 | img := image.NewRGBA(rect) 108 | 109 | black := &color.RGBA{ 110 | R: 0, 111 | G: 0, 112 | B: 0, 113 | A: 255, 114 | } 115 | for x := 0; x < width; x++ { 116 | for y := 0; y < height; y++ { 117 | img.Set(x, y, black) 118 | } 119 | } 120 | 121 | scale := 1.0 / float64(stats.Size()) * float64(height) 122 | c := &color.RGBA{ 123 | R: 255, 124 | G: 0, 125 | B: 0, 126 | A: 255, 127 | } 128 | 129 | var maxReadSize int64 = 1 130 | for _, op := range is.Stats.Reads { 131 | if op.Size > maxReadSize { 132 | maxReadSize = op.Size 133 | } 134 | } 135 | 136 | for x, op := range is.Stats.Reads { 137 | ymin := int(math.Floor(float64(op.Offset) * scale)) 138 | ymax := int(math.Ceil(float64(op.Offset+op.Size) * scale)) 139 | 140 | cd := *c 141 | cd.G = uint8(float64(op.Size) / float64(maxReadSize) * 255) 142 | 143 | for y := ymin; y <= ymax; y++ { 144 | img.Set(x, y, &cd) 145 | } 146 | } 147 | 148 | imageFile, err := os.Create("out/reads.png") 149 | must(err) 150 | defer imageFile.Close() 151 | 152 | err = png.Encode(imageFile, img) 153 | must(err) 154 | } 155 | 156 | func must(err error) { 157 | if err != nil { 158 | log.Panic(err) 159 | } 160 | } 161 | 162 | func (e *ecs) GetStream(item *sz.Item) (*sz.OutStream, error) { 163 | propPath, ok := item.GetStringProperty(sz.PidPath) 164 | if !ok { 165 | return nil, errors.New("could not get item path") 166 | } 167 | 168 | outPath := filepath.ToSlash(propPath) 169 | // Remove illegal character for windows paths, see 170 | // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx 171 | for i := byte(0); i <= 31; i++ { 172 | outPath = strings.Replace(outPath, string([]byte{i}), "_", -1) 173 | } 174 | 175 | absoluteOutPath := filepath.Join("out", outPath) 176 | 177 | log.Printf(" ") 178 | log.Printf("==> Extracting %d: %s", item.GetArchiveIndex(), outPath) 179 | 180 | if attrib, ok := item.GetUInt64Property(sz.PidAttrib); ok { 181 | log.Printf("==> Attrib %08x", attrib) 182 | } 183 | if attrib, ok := item.GetUInt64Property(sz.PidPosixAttrib); ok { 184 | log.Printf("==> Posix Attrib %08x", attrib) 185 | } 186 | if symlink, ok := item.GetStringProperty(sz.PidSymLink); ok { 187 | log.Printf("==> Symlink dest: %s", symlink) 188 | } 189 | 190 | isDir, _ := item.GetBoolProperty(sz.PidIsDir) 191 | if isDir { 192 | log.Printf("Making %s", outPath) 193 | 194 | err := os.MkdirAll(absoluteOutPath, 0755) 195 | if err != nil { 196 | return nil, err 197 | } 198 | 199 | // is a dir, just skip it 200 | return nil, nil 201 | } 202 | 203 | err := os.MkdirAll(filepath.Dir(absoluteOutPath), 0755) 204 | if err != nil { 205 | return nil, err 206 | } 207 | 208 | of, err := os.Create(absoluteOutPath) 209 | if err != nil { 210 | return nil, err 211 | } 212 | 213 | os, err := sz.NewOutStream(of) 214 | if err != nil { 215 | return nil, err 216 | } 217 | 218 | return os, nil 219 | } 220 | 221 | func (e *ecs) SetProgress(complete int64, total int64) { 222 | log.Printf("Progress: %s / %s", 223 | united.FormatBytes(complete), 224 | united.FormatBytes(total), 225 | ) 226 | } 227 | -------------------------------------------------------------------------------- /sz/glue.c: -------------------------------------------------------------------------------- 1 | 2 | #define GLUE_IMPLEMENT 1 3 | #include "glue.h" 4 | 5 | #include "_cgo_export.h" 6 | 7 | #include 8 | 9 | #ifdef __WIN32 10 | #include 11 | #define MY_DLOPEN LoadLibrary 12 | #define MY_DLSYM GetProcAddress 13 | #define MY_LIBHANDLE HMODULE 14 | #else // __WIN32 15 | #include 16 | #define MY_DLOPEN(x) dlopen((x), RTLD_LAZY|RTLD_LOCAL) 17 | #define MY_DLSYM dlsym 18 | #define MY_LIBHANDLE void* 19 | #endif // !__WIN32 20 | 21 | #define LOADSYM(sym) { \ 22 | sym ## _ = (void*) MY_DLSYM(dynlib, #sym); \ 23 | if (! sym ## _) { \ 24 | fprintf(stderr, "Could not load symbol %s\n", #sym); \ 25 | fflush(stderr); \ 26 | return 1; \ 27 | } \ 28 | } 29 | 30 | MY_LIBHANDLE dynlib; 31 | 32 | int libc7zip_initialize(char *lib_path) { 33 | dynlib = MY_DLOPEN(lib_path); 34 | if (!dynlib) { 35 | fprintf(stderr, "Could not load %s\n", lib_path); 36 | fflush(stderr); 37 | return 1; 38 | } 39 | 40 | LOADSYM(lib_new) 41 | LOADSYM(lib_get_last_error) 42 | LOADSYM(lib_get_version) 43 | LOADSYM(lib_free) 44 | 45 | LOADSYM(in_stream_new) 46 | LOADSYM(in_stream_get_def) 47 | LOADSYM(in_stream_commit_def) 48 | LOADSYM(in_stream_free) 49 | 50 | LOADSYM(out_stream_new) 51 | LOADSYM(out_stream_get_def) 52 | LOADSYM(out_stream_free) 53 | 54 | LOADSYM(archive_open) 55 | LOADSYM(archive_close) 56 | LOADSYM(archive_free) 57 | LOADSYM(archive_get_archive_format) 58 | LOADSYM(archive_get_item_count) 59 | LOADSYM(archive_get_item) 60 | 61 | LOADSYM(item_get_archive_index) 62 | LOADSYM(item_get_string_property) 63 | LOADSYM(string_free) 64 | LOADSYM(item_get_uint64_property) 65 | LOADSYM(item_get_bool_property) 66 | LOADSYM(item_free) 67 | 68 | LOADSYM(archive_extract_item) 69 | LOADSYM(archive_extract_several) 70 | 71 | LOADSYM(extract_callback_new) 72 | LOADSYM(extract_callback_get_def) 73 | LOADSYM(extract_callback_free) 74 | 75 | return 0; 76 | } 77 | 78 | lib *libc7zip_lib_new() { 79 | return lib_new_(); 80 | } 81 | 82 | void libc7zip_lib_free(lib *l) { 83 | return lib_free_(l); 84 | } 85 | 86 | int32_t libc7zip_lib_get_last_error(lib *l) { 87 | return lib_get_last_error_(l); 88 | } 89 | 90 | char *libc7zip_lib_get_version(lib *l) { 91 | return lib_get_version_(l); 92 | } 93 | 94 | //----------------- 95 | 96 | in_stream *libc7zip_in_stream_new() { 97 | return in_stream_new_(); 98 | } 99 | 100 | in_stream_def *libc7zip_in_stream_get_def(in_stream *is) { 101 | return in_stream_get_def_(is); 102 | } 103 | 104 | void libc7zip_in_stream_commit_def(in_stream *is) { 105 | in_stream_commit_def_(is); 106 | } 107 | 108 | void libc7zip_in_stream_free(in_stream *is) { 109 | return in_stream_free_(is); 110 | } 111 | 112 | //----------------- 113 | 114 | out_stream *libc7zip_out_stream_new() { 115 | return out_stream_new_(); 116 | } 117 | 118 | out_stream_def *libc7zip_out_stream_get_def(out_stream *os) { 119 | return out_stream_get_def_(os); 120 | } 121 | 122 | void libc7zip_out_stream_free(out_stream *os) { 123 | return out_stream_free_(os); 124 | } 125 | 126 | //----------------- 127 | 128 | archive *libc7zip_archive_open(lib *l, in_stream *is, int32_t by_signature) { 129 | return archive_open_(l, is, by_signature); 130 | } 131 | 132 | void libc7zip_archive_close(archive *a) { 133 | return archive_close_(a); 134 | } 135 | 136 | void libc7zip_archive_free(archive *a) { 137 | return archive_free_(a); 138 | } 139 | 140 | char *libc7zip_archive_get_archive_format(archive *a) { 141 | return archive_get_archive_format_(a); 142 | } 143 | 144 | int64_t libc7zip_archive_get_item_count(archive *a) { 145 | return archive_get_item_count_(a); 146 | } 147 | 148 | item *libc7zip_archive_get_item(archive *a, int64_t index) { 149 | return archive_get_item_(a, index); 150 | } 151 | 152 | int32_t libc7zip_item_get_archive_index(item *i) { 153 | return item_get_archive_index_(i); 154 | } 155 | 156 | char *libc7zip_item_get_string_property(item *i, int32_t property_index, int32_t *success) { 157 | return item_get_string_property_(i, property_index, success); 158 | } 159 | 160 | void libc7zip_string_free(char *s) { 161 | string_free_(s); 162 | } 163 | 164 | uint64_t libc7zip_item_get_uint64_property(item *i, int32_t property_index, int32_t *success) { 165 | return item_get_uint64_property_(i, property_index, success); 166 | } 167 | 168 | int32_t libc7zip_item_get_bool_property(item *i, int32_t property_index, int32_t *success) { 169 | return item_get_bool_property_(i, property_index, success); 170 | } 171 | 172 | void libc7zip_item_free(item *i) { 173 | return item_free_(i); 174 | } 175 | 176 | int libc7zip_archive_extract_item(archive *a, item *i, out_stream *os) { 177 | return archive_extract_item_(a, i, os); 178 | } 179 | 180 | int libc7zip_archive_extract_several(archive *a, int64_t *indices, int32_t num_indices, extract_callback *ec) { 181 | return archive_extract_several_(a, indices, num_indices, ec); 182 | } 183 | 184 | //----------------- 185 | 186 | extract_callback *libc7zip_extract_callback_new() { 187 | return extract_callback_new_(); 188 | } 189 | 190 | extract_callback_def *libc7zip_extract_callback_get_def(extract_callback *ec) { 191 | return extract_callback_get_def_(ec); 192 | } 193 | 194 | void libc7zip_extract_callback_free(extract_callback *ec) { 195 | extract_callback_free_(ec); 196 | } 197 | 198 | // Gateway functions 199 | 200 | int inSeekGo_cgo(int64_t id, int64_t offset, int32_t whence, int64_t *new_position) { 201 | return inSeekGo(id, offset, whence, new_position); 202 | } 203 | 204 | int inReadGo_cgo(int64_t id, void *data, int64_t size, int64_t *processed_size) { 205 | return inReadGo(id, data, size, processed_size); 206 | } 207 | 208 | int outWriteGo_cgo(int64_t id, const void *data, int64_t size, int64_t *processed_size) { 209 | return outWriteGo(id, (void*) data, size, processed_size); 210 | } 211 | 212 | void ecSetTotalGo_cgo(int64_t id, int64_t size) { 213 | ecSetTotalGo(id, size); 214 | } 215 | 216 | void ecSetCompletedGo_cgo(int64_t id, int64_t size) { 217 | ecSetCompletedGo(id, size); 218 | } 219 | 220 | out_stream *ecGetStreamGo_cgo(int64_t id, int64_t index) { 221 | return ecGetStreamGo(id, index); 222 | } 223 | 224 | void ecSetOperationResultGo_cgo(int64_t id, int32_t result) { 225 | ecSetOperationResultGo(id, result); 226 | } 227 | 228 | -------------------------------------------------------------------------------- /sz/glue.go: -------------------------------------------------------------------------------- 1 | package sz 2 | 3 | /* 4 | #cgo !windows LDFLAGS: -ldl 5 | 6 | #include // for C.free 7 | #include "glue.h" 8 | 9 | // forward declaration for gateway functions 10 | int inReadGo_cgo(int64_t id, void *data, int64_t size, int64_t *processed_size); 11 | int inSeekGo_cgo(int64_t id, int64_t offset, int32_t whence, int64_t *new_position); 12 | 13 | int outWriteGo_cgo(int64_t id, const void *data, int64_t size, int64_t *processed_size); 14 | 15 | void ecSetTotalGo_cgo(int64_t id, int64_t size); 16 | void ecSetCompletedGo_cgo(int64_t id, int64_t size); 17 | out_stream *ecGetStreamGo_cgo(int64_t id, int64_t index); 18 | void ecSetOperationResultGo_cgo(int64_t id, int32_t result); 19 | */ 20 | import "C" 21 | import ( 22 | "errors" 23 | "fmt" 24 | "io" 25 | "os" 26 | "path/filepath" 27 | "reflect" 28 | "runtime" 29 | "unsafe" 30 | ) 31 | 32 | type ReaderAtCloser interface { 33 | io.ReaderAt 34 | io.Closer 35 | } 36 | 37 | type InStream struct { 38 | reader ReaderAtCloser 39 | size int64 40 | id int64 41 | offset int64 42 | strm *C.in_stream 43 | err error 44 | 45 | Stats *ReadStats 46 | 47 | ChunkSize int64 48 | } 49 | 50 | type OutStream struct { 51 | writer io.WriteCloser 52 | id int64 53 | strm *C.out_stream 54 | closed bool 55 | err error 56 | 57 | ChunkSize int64 58 | } 59 | 60 | type Lib struct { 61 | lib *C.lib 62 | } 63 | 64 | var lazyInitDone = false 65 | 66 | func lazyInit() error { 67 | if lazyInitDone { 68 | return nil 69 | } 70 | 71 | libPath := "unsupported-os" 72 | switch runtime.GOOS { 73 | case "windows": 74 | libPath = "c7zip.dll" 75 | case "linux": 76 | libPath = "libc7zip.so" 77 | case "darwin": 78 | libPath = "libc7zip.dylib" 79 | } 80 | 81 | execPath, err := os.Executable() 82 | if err != nil { 83 | return err 84 | } 85 | 86 | libPath = filepath.Join(filepath.Dir(execPath), libPath) 87 | 88 | cLibPath := C.CString(libPath) 89 | defer C.free(unsafe.Pointer(cLibPath)) 90 | 91 | ret := C.libc7zip_initialize(cLibPath) 92 | if ret != 0 { 93 | return fmt.Errorf("could not initialize libc7zip") 94 | } 95 | 96 | lazyInitDone = true 97 | return nil 98 | } 99 | 100 | func NewLib() (*Lib, error) { 101 | err := lazyInit() 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | lib := C.libc7zip_lib_new() 107 | if lib == nil { 108 | return nil, fmt.Errorf("could not create new lib") 109 | } 110 | 111 | l := &Lib{ 112 | lib: lib, 113 | } 114 | return l, nil 115 | } 116 | 117 | func (l *Lib) Error() error { 118 | le := (ErrorCode)(C.libc7zip_lib_get_last_error(l.lib)) 119 | switch le { 120 | case ErrCodeNoError: 121 | return nil 122 | case ErrCodeUnknownError: 123 | return ErrUnknownError 124 | case ErrCodeNotInitialize: 125 | return ErrNotInitialize 126 | case ErrCodeNeedPassword: 127 | return ErrNeedPassword 128 | case ErrCodeNotSupportedArchive: 129 | return ErrNotSupportedArchive 130 | default: 131 | return ErrUnknownError 132 | } 133 | } 134 | 135 | func (l *Lib) GetVersion() string { 136 | // refers to static memory, so it should never be freed 137 | return C.GoString(C.libc7zip_lib_get_version(l.lib)) 138 | } 139 | 140 | func (l *Lib) Free() { 141 | C.libc7zip_lib_free(l.lib) 142 | } 143 | 144 | func NewInStream(reader ReaderAtCloser, ext string, size int64) (*InStream, error) { 145 | strm := C.libc7zip_in_stream_new() 146 | if strm == nil { 147 | return nil, fmt.Errorf("could not create new InStream") 148 | } 149 | 150 | in := &InStream{ 151 | reader: reader, 152 | size: size, 153 | offset: 0, 154 | strm: strm, 155 | } 156 | reserveInStreamId(in) 157 | 158 | def := C.libc7zip_in_stream_get_def(strm) 159 | def.size = C.int64_t(in.size) 160 | def.ext = C.CString(ext) 161 | def.id = C.int64_t(in.id) 162 | def.read_cb = (C.read_cb_t)(unsafe.Pointer(C.inReadGo_cgo)) 163 | def.seek_cb = (C.seek_cb_t)(unsafe.Pointer(C.inSeekGo_cgo)) 164 | 165 | C.libc7zip_in_stream_commit_def(strm) 166 | 167 | return in, nil 168 | } 169 | 170 | func (in *InStream) SetExt(ext string) { 171 | strm := in.strm 172 | if strm == nil { 173 | return 174 | } 175 | 176 | def := C.libc7zip_in_stream_get_def(strm) 177 | def.ext = C.CString(ext) 178 | C.libc7zip_in_stream_commit_def(strm) 179 | } 180 | 181 | func (in *InStream) Seek(offset int64, whence int) (int64, error) { 182 | switch whence { 183 | case io.SeekStart: 184 | in.offset = offset 185 | case io.SeekCurrent: 186 | in.offset += offset 187 | case io.SeekEnd: 188 | in.offset = in.size + offset 189 | } 190 | 191 | return in.offset, nil 192 | } 193 | 194 | func (in *InStream) Free() { 195 | if in.id > 0 { 196 | freeInStreamId(in.id) 197 | in.id = 0 198 | } 199 | 200 | if in.strm != nil { 201 | C.libc7zip_in_stream_free(in.strm) 202 | in.strm = nil 203 | } 204 | } 205 | 206 | func (in *InStream) Error() error { 207 | return in.err 208 | } 209 | 210 | func NewOutStream(writer io.WriteCloser) (*OutStream, error) { 211 | strm := C.libc7zip_out_stream_new() 212 | if strm == nil { 213 | return nil, fmt.Errorf("could not create new OutStream") 214 | } 215 | 216 | out := &OutStream{ 217 | writer: writer, 218 | strm: strm, 219 | } 220 | reserveOutStreamId(out) 221 | 222 | def := C.libc7zip_out_stream_get_def(strm) 223 | def.id = C.int64_t(out.id) 224 | def.write_cb = (C.write_cb_t)(unsafe.Pointer(C.outWriteGo_cgo)) 225 | 226 | return out, nil 227 | } 228 | 229 | func (out *OutStream) Close() error { 230 | if out.id > 0 { 231 | freeOutStreamId(out.id) 232 | out.id = 0 233 | return out.writer.Close() 234 | } 235 | 236 | // already closed 237 | return nil 238 | } 239 | 240 | func (out *OutStream) Free() { 241 | if out.strm != nil { 242 | C.libc7zip_out_stream_free(out.strm) 243 | out.strm = nil 244 | } 245 | } 246 | 247 | func (out *OutStream) Error() error { 248 | return out.err 249 | } 250 | 251 | type Archive struct { 252 | arch *C.archive 253 | in *InStream 254 | lib *Lib 255 | } 256 | 257 | func (lib *Lib) OpenArchive(in *InStream, bySignature bool) (*Archive, error) { 258 | cBySignature := C.int32_t(0) 259 | if bySignature { 260 | cBySignature = 1 261 | } 262 | 263 | arch := C.libc7zip_archive_open(lib.lib, in.strm, cBySignature) 264 | if arch == nil { 265 | err := coalesceErrors(in.Error(), lib.Error(), ErrUnknownError) 266 | return nil, err 267 | } 268 | 269 | a := &Archive{ 270 | arch: arch, 271 | in: in, 272 | lib: lib, 273 | } 274 | return a, nil 275 | } 276 | 277 | func (a *Archive) Close() { 278 | C.libc7zip_archive_close(a.arch) 279 | } 280 | 281 | func (a *Archive) Free() { 282 | C.libc7zip_archive_free(a.arch) 283 | } 284 | 285 | func (a *Archive) GetArchiveFormat() string { 286 | cstr := C.libc7zip_archive_get_archive_format(a.arch) 287 | if cstr == nil { 288 | return "" 289 | } 290 | 291 | defer C.libc7zip_string_free(cstr) 292 | return C.GoString(cstr) 293 | } 294 | 295 | func (a *Archive) GetItemCount() (int64, error) { 296 | res := int64(C.libc7zip_archive_get_item_count(a.arch)) 297 | if res < 0 { 298 | err := coalesceErrors(a.in.Error(), a.lib.Error(), ErrUnknownError) 299 | return 0, err 300 | } 301 | return res, nil 302 | } 303 | 304 | func coalesceErrors(errors ...error) error { 305 | for _, e := range errors { 306 | if e != nil { 307 | return e 308 | } 309 | } 310 | return nil 311 | } 312 | 313 | type Item struct { 314 | item *C.item 315 | } 316 | 317 | func (a *Archive) GetItem(index int64) *Item { 318 | item := C.libc7zip_archive_get_item(a.arch, C.int64_t(index)) 319 | if item == nil { 320 | return nil 321 | } 322 | 323 | return &Item{ 324 | item: item, 325 | } 326 | } 327 | 328 | type PropertyIndex int32 329 | 330 | var ( 331 | // Packed Size 332 | PidPackSize PropertyIndex = C.kpidPackSize 333 | // Attributes 334 | PidAttrib PropertyIndex = C.kpidAttrib 335 | // Created 336 | PidCTime PropertyIndex = C.kpidCTime 337 | // Accessed 338 | PidATime PropertyIndex = C.kpidATime 339 | // Modified 340 | PidMTime PropertyIndex = C.kpidMTime 341 | // Solid 342 | PidSolid PropertyIndex = C.kpidSolid 343 | // Encrypted 344 | PidEncrypted PropertyIndex = C.kpidEncrypted 345 | // User 346 | PidUser PropertyIndex = C.kpidUser 347 | // Group 348 | PidGroup PropertyIndex = C.kpidGroup 349 | // Comment 350 | PidComment PropertyIndex = C.kpidComment 351 | // Physical Size 352 | PidPhySize PropertyIndex = C.kpidPhySize 353 | // Headers Size 354 | PidHeadersSize PropertyIndex = C.kpidHeadersSize 355 | // Checksum 356 | PidChecksum PropertyIndex = C.kpidChecksum 357 | // Characteristics 358 | PidCharacts PropertyIndex = C.kpidCharacts 359 | // Creator Application 360 | PidCreatorApp PropertyIndex = C.kpidCreatorApp 361 | // Total Size 362 | PidTotalSize PropertyIndex = C.kpidTotalSize 363 | // Free Space 364 | PidFreeSpace PropertyIndex = C.kpidFreeSpace 365 | // Cluster Size 366 | PidClusterSize PropertyIndex = C.kpidClusterSize 367 | // Label 368 | PidVolumeName PropertyIndex = C.kpidVolumeName 369 | // FullPath 370 | PidPath PropertyIndex = C.kpidPath 371 | // IsDir 372 | PidIsDir PropertyIndex = C.kpidIsDir 373 | // Uncompressed Size 374 | PidSize PropertyIndex = C.kpidSize 375 | // Symbolic link destination 376 | PidSymLink PropertyIndex = C.kpidSymLink 377 | // POSIX attributes 378 | PidPosixAttrib PropertyIndex = C.kpidPosixAttrib 379 | ) 380 | 381 | type ErrorCode int32 382 | 383 | var ( 384 | ErrCodeNoError ErrorCode = C.LIB7ZIP_NO_ERROR 385 | 386 | ErrCodeUnknownError ErrorCode = C.LIB7ZIP_UNKNOWN_ERROR 387 | ErrUnknownError = errors.New("Unknown 7-zip error") 388 | 389 | ErrCodeNotInitialize ErrorCode = C.LIB7ZIP_NOT_INITIALIZE 390 | ErrNotInitialize = errors.New("7-zip not initialized") 391 | 392 | ErrCodeNeedPassword ErrorCode = C.LIB7ZIP_NEED_PASSWORD 393 | ErrNeedPassword = errors.New("Password required to extract archive with 7-zip") 394 | 395 | ErrCodeNotSupportedArchive ErrorCode = C.LIB7ZIP_NOT_SUPPORTED_ARCHIVE 396 | ErrNotSupportedArchive = errors.New("Archive type not supported by 7-zip") 397 | ) 398 | 399 | func (i *Item) GetArchiveIndex() int64 { 400 | return int64(C.libc7zip_item_get_archive_index(i.item)) 401 | } 402 | 403 | func (i *Item) GetStringProperty(id PropertyIndex) (string, bool) { 404 | var success = C.int32_t(0) 405 | 406 | cstr := C.libc7zip_item_get_string_property(i.item, C.int32_t(id), &success) 407 | if cstr == nil { 408 | return "", false 409 | } 410 | 411 | defer C.libc7zip_string_free(cstr) 412 | return C.GoString(cstr), success == 1 413 | } 414 | 415 | func (i *Item) GetUInt64Property(id PropertyIndex) (uint64, bool) { 416 | var success = C.int32_t(0) 417 | val := uint64(C.libc7zip_item_get_uint64_property(i.item, C.int32_t(id), &success)) 418 | return val, success == 1 419 | } 420 | 421 | func (i *Item) GetBoolProperty(id PropertyIndex) (bool, bool) { 422 | var success = C.int32_t(0) 423 | val := C.libc7zip_item_get_bool_property(i.item, C.int32_t(id), &success) != 0 424 | return val, success == 1 425 | } 426 | 427 | func (i *Item) Free() { 428 | C.libc7zip_item_free(i.item) 429 | } 430 | 431 | func (a *Archive) Extract(i *Item, out *OutStream) error { 432 | // returns a boolean, truthiness indicates success 433 | success := C.libc7zip_archive_extract_item(a.arch, i.item, out.strm) 434 | if success == 0 { 435 | err := coalesceErrors(a.in.Error(), out.Error(), a.lib.Error(), ErrUnknownError) 436 | return err 437 | } 438 | 439 | return nil 440 | } 441 | 442 | type ExtractCallbackFuncs interface { 443 | SetProgress(completed int64, total int64) 444 | GetStream(item *Item) (*OutStream, error) 445 | } 446 | 447 | type ExtractCallback struct { 448 | id int64 449 | cb *C.extract_callback 450 | funcs ExtractCallbackFuncs 451 | 452 | total int64 453 | archive *Archive 454 | item *Item 455 | out *OutStream 456 | errors []error 457 | } 458 | 459 | func NewExtractCallback(funcs ExtractCallbackFuncs) (*ExtractCallback, error) { 460 | cb := C.libc7zip_extract_callback_new() 461 | if cb == nil { 462 | return nil, fmt.Errorf("could not create new ExtractCallback") 463 | } 464 | 465 | ec := &ExtractCallback{ 466 | funcs: funcs, 467 | cb: cb, 468 | } 469 | reserveExtractCallbackId(ec) 470 | 471 | def := C.libc7zip_extract_callback_get_def(cb) 472 | def.id = C.int64_t(ec.id) 473 | def.set_total_cb = (C.set_total_cb_t)(unsafe.Pointer(C.ecSetTotalGo_cgo)) 474 | def.set_completed_cb = (C.set_completed_cb_t)(unsafe.Pointer(C.ecSetCompletedGo_cgo)) 475 | def.get_stream_cb = (C.get_stream_cb_t)(unsafe.Pointer(C.ecGetStreamGo_cgo)) 476 | def.set_operation_result_cb = (C.set_operation_result_cb_t)(unsafe.Pointer(C.ecSetOperationResultGo_cgo)) 477 | 478 | return ec, nil 479 | } 480 | 481 | func (ec *ExtractCallback) Errors() []error { 482 | return ec.errors 483 | } 484 | 485 | func (ec *ExtractCallback) Free() { 486 | C.libc7zip_extract_callback_free(ec.cb) 487 | } 488 | 489 | func (a *Archive) ExtractSeveral(indices []int64, ec *ExtractCallback) error { 490 | ec.archive = a 491 | 492 | // returns a boolean, truthiness indicates success 493 | success := C.libc7zip_archive_extract_several(a.arch, (*C.int64_t)(unsafe.Pointer(&indices[0])), C.int32_t(len(indices)), ec.cb) 494 | ec.archive = nil 495 | if success == 0 { 496 | err := coalesceErrors(a.in.Error(), a.lib.Error(), ErrUnknownError) 497 | return err 498 | } 499 | 500 | return nil 501 | } 502 | 503 | //export inSeekGo 504 | func inSeekGo(id int64, offset int64, whence int32, newPosition unsafe.Pointer) int { 505 | p, ok := inStreams.Load(id) 506 | if !ok { 507 | return 1 508 | } 509 | in, ok := (p).(*InStream) 510 | if !ok { 511 | return 1 512 | } 513 | 514 | newOffset, err := in.Seek(offset, int(whence)) 515 | if err != nil { 516 | in.err = err 517 | return 1 518 | } 519 | 520 | newPosPtr := (*int64)(newPosition) 521 | *newPosPtr = newOffset 522 | 523 | in.err = nil 524 | return 0 525 | } 526 | 527 | //export inReadGo 528 | func inReadGo(id int64, data unsafe.Pointer, size int64, processedSize unsafe.Pointer) int { 529 | p, ok := inStreams.Load(id) 530 | if !ok { 531 | return 1 532 | } 533 | in, ok := (p).(*InStream) 534 | if !ok { 535 | return 1 536 | } 537 | 538 | if in.ChunkSize > 0 && size > in.ChunkSize { 539 | size = in.ChunkSize 540 | } 541 | 542 | if in.offset+size > in.size { 543 | size = in.size - in.offset 544 | } 545 | 546 | if in.Stats != nil { 547 | in.Stats.RecordRead(in.offset, size) 548 | } 549 | 550 | h := reflect.SliceHeader{ 551 | Data: uintptr(data), 552 | Cap: int(size), 553 | Len: int(size), 554 | } 555 | buf := *(*[]byte)(unsafe.Pointer(&h)) 556 | 557 | readBytes, err := in.reader.ReadAt(buf, in.offset) 558 | if err != nil { 559 | in.err = err 560 | return 1 561 | } 562 | 563 | in.offset += int64(readBytes) 564 | 565 | processedSizePtr := (*int64)(processedSize) 566 | *processedSizePtr = int64(readBytes) 567 | 568 | in.err = nil 569 | return 0 570 | } 571 | 572 | //export outWriteGo 573 | func outWriteGo(id int64, data unsafe.Pointer, size int64, processedSize unsafe.Pointer) int { 574 | p, ok := outStreams.Load(id) 575 | if !ok { 576 | return 1 577 | } 578 | out, ok := (p).(*OutStream) 579 | if !ok { 580 | return 1 581 | } 582 | 583 | if out.ChunkSize > 0 && size > out.ChunkSize { 584 | size = out.ChunkSize 585 | } 586 | 587 | h := reflect.SliceHeader{ 588 | Data: uintptr(data), 589 | Cap: int(size), 590 | Len: int(size), 591 | } 592 | buf := *(*[]byte)(unsafe.Pointer(&h)) 593 | 594 | writtenBytes, err := out.writer.Write(buf) 595 | if err != nil { 596 | out.err = err 597 | return 1 598 | } 599 | 600 | processedSizePtr := (*int64)(processedSize) 601 | *processedSizePtr = int64(writtenBytes) 602 | 603 | out.err = nil 604 | return 0 605 | } 606 | 607 | //export ecSetTotalGo 608 | func ecSetTotalGo(id int64, size int64) { 609 | p, ok := extractCallbacks.Load(id) 610 | if !ok { 611 | return 612 | } 613 | ec, ok := (p).(*ExtractCallback) 614 | if !ok { 615 | return 616 | } 617 | 618 | ec.total = size 619 | } 620 | 621 | //export ecSetCompletedGo 622 | func ecSetCompletedGo(id int64, completed int64) { 623 | p, ok := extractCallbacks.Load(id) 624 | if !ok { 625 | return 626 | } 627 | ec, ok := (p).(*ExtractCallback) 628 | if !ok { 629 | return 630 | } 631 | 632 | ec.funcs.SetProgress(completed, ec.total) 633 | } 634 | 635 | //export ecGetStreamGo 636 | func ecGetStreamGo(id int64, index int64) *C.out_stream { 637 | p, ok := extractCallbacks.Load(id) 638 | if !ok { 639 | return nil 640 | } 641 | ec, ok := (p).(*ExtractCallback) 642 | if !ok { 643 | return nil 644 | } 645 | 646 | ec.item = ec.archive.GetItem(int64(index)) 647 | if ec.item == nil { 648 | ec.errors = append(ec.errors, fmt.Errorf("sz: no Item for index %d", index)) 649 | return nil 650 | } 651 | 652 | out, err := ec.funcs.GetStream(ec.item) 653 | if err != nil { 654 | ec.errors = append(ec.errors, err) 655 | return nil 656 | } 657 | 658 | if out != nil { 659 | ec.out = out 660 | return out.strm 661 | } 662 | return nil 663 | } 664 | 665 | //export ecSetOperationResultGo 666 | func ecSetOperationResultGo(id int64, result int32) { 667 | p, ok := extractCallbacks.Load(id) 668 | if !ok { 669 | return 670 | } 671 | ec, ok := (p).(*ExtractCallback) 672 | if !ok { 673 | return 674 | } 675 | 676 | if ec.item != nil { 677 | ec.item.Free() 678 | ec.item = nil 679 | } 680 | 681 | if ec.out != nil { 682 | err := ec.out.Close() 683 | if err != nil { 684 | ec.errors = append(ec.errors, err) 685 | } 686 | ec.out.Free() 687 | ec.out = nil 688 | } 689 | 690 | // TODO: so, if result isn't NArchive::NExtract::NOperationResult::kOK 691 | // then something went wrong with the extraction, should we call 692 | // GetLastError() and append it somewhere ? 693 | if result != 0 { 694 | err := coalesceErrors(ec.archive.lib.Error(), ErrUnknownError) 695 | ec.errors = append(ec.errors, err) 696 | } 697 | } 698 | --------------------------------------------------------------------------------