├── .gitignore ├── tests ├── tests.7z └── tnim7z.nim ├── nim7z.nimble ├── LICENSE ├── nim7z.cfg ├── README.md └── nim7z.nim /.gitignore: -------------------------------------------------------------------------------- 1 | nim7z 2 | nimcache 3 | *.exe 4 | *.swp 5 | test* 6 | -------------------------------------------------------------------------------- /tests/tests.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genotrance/nim7z/HEAD/tests/tests.7z -------------------------------------------------------------------------------- /tests/tnim7z.nim: -------------------------------------------------------------------------------- 1 | import nim7z 2 | 3 | import os, strutils 4 | 5 | removeDir("tempdir") 6 | 7 | let svnzf = new7zFile("tests/tests.7z") 8 | extract(svnzf, "tempdir") 9 | 10 | assert fileExists("tempdir"/"nimcache"/"nim7z_test7z.c") 11 | 12 | while dirExists("tempdir"): 13 | try: 14 | removeDir("tempdir") 15 | sleep(1000) 16 | except: 17 | discard 18 | 19 | extract(svnzf, "tempdir", true) 20 | 21 | assert fileExists("tempdir"/"nim7z_test7z.c") 22 | 23 | removeDir("tempdir") 24 | 25 | try: 26 | extract("nim7z.nimble", "tempdir") 27 | except SvnzError: 28 | if "17" notin getCurrentExceptionMsg(): 29 | quit(1) 30 | 31 | -------------------------------------------------------------------------------- /nim7z.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.1.5" 4 | author = "genotrance" 5 | description = "7z extraction for Nim" 6 | license = "MIT" 7 | 8 | skipDirs = @["tests"] 9 | 10 | # Dependencies 11 | 12 | requires "nimgen >= 0.4.0" 13 | 14 | var 15 | name = "nim7z" 16 | cmd = when defined(Windows): "cmd /c " else: "" 17 | 18 | mkDir(name) 19 | 20 | task setup, "Checkout and generate": 21 | if gorgeEx(cmd & "nimgen").exitCode != 0: 22 | withDir(".."): 23 | exec "nimble install nimgen -y" 24 | exec cmd & "nimgen " & name & ".cfg" 25 | 26 | before install: 27 | setupTask() 28 | 29 | task test, "Run tests": 30 | exec "nim c -r tests/t" & name & ".nim" 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ganesh Viswanathan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /nim7z.cfg: -------------------------------------------------------------------------------- 1 | [n.global] 2 | output = nim7z 3 | 4 | [n.include] 5 | nim7z/C 6 | 7 | [n.prepare] 8 | gitremote = "https://github.com/kornelski/7z" 9 | gitsparse = """ 10 | C/* 11 | """ 12 | 13 | [n.wildcard] 14 | wildcard = "7*.h" 15 | rename = "$replace(7=svn)" 16 | 17 | wildcard.n = "*.nim" 18 | search = csize 19 | replace = csize_t 20 | 21 | [7z.h] 22 | preprocess = true 23 | defines = true 24 | recurse = true 25 | 26 | compile = nim7z/C 27 | pragma-lin = "passC: \"-D_7ZIP_ST\"" 28 | pragma-mac = "passC: \"-D_7ZIP_ST\"" 29 | 30 | [7zAlloc.h] 31 | preprocess = true 32 | defines = true 33 | recurse = true 34 | 35 | [7zBuf.h] 36 | preprocess = true 37 | defines = true 38 | recurse = true 39 | 40 | [7zCrc.h] 41 | preprocess = true 42 | defines = true 43 | recurse = true 44 | 45 | [7zFile.h] 46 | preprocess = true 47 | defines = true 48 | recurse = true 49 | 50 | [7zVersion.h] 51 | preprocess = true 52 | defines = true 53 | recurse = true 54 | 55 | [svnz.nim] 56 | search-lin.1 = "{.compile: \"nim7z/C/LzFindMt.c" 57 | comment-lin.1 = 1 58 | 59 | search-lin.2 = "{.compile: \"nim7z/C/Threads.c" 60 | comment-lin.2 = 1 61 | 62 | search-mac.1 = "{.compile: \"nim7z/C/LzFindMt.c" 63 | comment-mac.1 = 1 64 | 65 | search-mac.2 = "{.compile: \"nim7z/C/Threads.c" 66 | comment-mac.2 = 1 67 | 68 | [svnzTypes.nim] 69 | search = MY__FACILITY_WIN32 70 | replace = MY_FACILITY_WIN32 71 | 72 | [svnzFile.nim] 73 | search = HANDLE 74 | replace = int 75 | 76 | search.2 = WCHAR 77 | replace.2 = Utf16Char 78 | 79 | [svnzCrc.nim] 80 | search = var g_CrcTable 81 | comment = 1 82 | 83 | [svnzAlloc.nim] 84 | dummy -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nim7z is a [Nim](https://nim-lang.org/) wrapper for the [7zip decoder](https://github.com/kornelski/7z). 2 | 3 | Nim7z is distributed as a [Nimble](https://github.com/nim-lang/nimble) package and depends on [nimgen](https://github.com/genotrance/nimgen) and [c2nim](https://github.com/nim-lang/c2nim/) to generate the wrappers. The 7zip source code is downloaded using Git so having ```git``` in the path is required. 4 | 5 | __Installation__ 6 | 7 | Nim7z can be installed via [Nimble](https://github.com/nim-lang/nimble): 8 | 9 | ``` 10 | > nimble install nim7z 11 | ``` 12 | 13 | This will download, wrap and install nim7z in the standard Nimble package location, typically ~/.nimble. Once installed, it can be imported into any Nim program. 14 | 15 | __Usage__ 16 | 17 | Module documentation can be found [here](http://nimgen.genotrance.com/nim7z). 18 | 19 | ```nim 20 | import nim7z 21 | 22 | let svnz = new7zFile("testfile.7z") 23 | extract(svnz, "outdir") 24 | 25 | extract("testfile.7z", "outdir", skipOuterDirs=true) 26 | ``` 27 | 28 | Refer to the ```tests``` directory for examples on how the library can be used. 29 | 30 | __Credits__ 31 | 32 | Nim7z wraps the 7zip source code and all licensing terms of [7zip](https://github.com/kornelski/7z) apply to the usage of this package. 33 | 34 | Credits go out to [c2nim](https://github.com/nim-lang/c2nim/) as well without which this package would be greatly limited in its abilities. 35 | 36 | __Feedback__ 37 | 38 | Nim7z is a work in progress and any feedback or suggestions are welcome. It is hosted on [GitHub](https://github.com/genotrance/nim7z) with an MIT license so issues, forks and PRs are most appreciated. 39 | -------------------------------------------------------------------------------- /nim7z.nim: -------------------------------------------------------------------------------- 1 | import nim7z/svnz, nim7z/svnzAlloc, nim7z/svnzCrc, nim7z/svnzFile, nim7z/svnzTypes 2 | 3 | import os, ospaths, sequtils, strutils 4 | 5 | template SzBitArray_Check(p, i: untyped): untyped = 6 | ((cast[ptr UncheckedArray[int8]](p))[(i) shr 3] and (0x80 shr ((i) and 7))) 7 | 8 | template SzArEx_IsDir(p, i: untyped): untyped = 9 | SzBitArray_Check(p.IsDirs, i) 10 | 11 | type 12 | SvnzFile* = ref SvnzFileObj 13 | SvnzFileObj = object 14 | filename: string 15 | archiveStream: CFileInStream 16 | lookStream: CLookToRead2 17 | db: CSzArEx 18 | 19 | SvnzError* = object of Exception 20 | 21 | var 22 | g_Alloc = ISzAlloc(Alloc: SzAlloc, Free: SzFree) 23 | allocImp = g_Alloc 24 | allocTempImp = g_Alloc 25 | kInputBufSize = 1.csize_t shl 18 26 | 27 | res: SRes = SZ_OK 28 | 29 | temp: ptr UInt16 30 | tempSize: csize_t = 0 31 | 32 | proc checkError() = 33 | if res != SZ_OK: 34 | if res == SZ_ERROR_UNSUPPORTED: 35 | raise newException(SvnzError, "Decoder doesn't support this archive") 36 | elif res == SZ_ERROR_MEM: 37 | raise newException(SvnzError, "Can not allocate memory") 38 | elif res == SZ_ERROR_CRC: 39 | raise newException(SvnzError, "CRC error") 40 | else: 41 | raise newException(SvnzError, "Value = " & $res) 42 | 43 | proc `=destroy`(svnz: var SvnzFileObj) = 44 | SzArEx_Free(addr svnz.db, addr allocImp) 45 | allocImp.Free(addr allocImp, svnz.lookStream.buf) 46 | 47 | discard File_Close(addr svnz.archiveStream.file) 48 | 49 | proc new7zFile*(fname: string): SvnzFile = 50 | ## Opens a .7z file for reading. 51 | result = SvnzFile(filename: fname) 52 | 53 | proc loadDataStream(svnz: SvnzFile) = 54 | if InFile_Open(addr svnz.archiveStream.file, svnz.filename) != 0: 55 | raise newException(SvnzError, "Failed to load file: " & svnz.filename) 56 | 57 | FileInStream_CreateVTable(addr svnz.archiveStream) 58 | LookToRead2_CreateVTable(addr svnz.lookStream, False) 59 | 60 | svnz.lookStream.buf = cast[ptr Byte](allocImp.Alloc(addr allocImp, kInputBufSize)) 61 | if svnz.lookStream.buf == nil: 62 | res = SZ_ERROR_MEM 63 | else: 64 | svnz.lookStream.bufSize = kInputBufSize 65 | svnz.lookStream.realStream = addr svnz.archiveStream.vt 66 | svnz.lookStream.pos = 0 67 | svnz.lookStream.size = 0 68 | 69 | CrcGenerateTable(); 70 | 71 | SzArEx_Init(addr svnz.db) 72 | 73 | if res == SZ_OK: 74 | res = SzArEx_Open(addr svnz.db, addr svnz.lookStream.vt, addr allocImp, addr allocTempImp) 75 | 76 | checkError() 77 | 78 | iterator walk(svnz: SvnzFile): tuple[filename: string, isdir: int, contents: ptr Byte, size: csize_t] = 79 | var 80 | blockIndex = 0xFFFFFFFF.uint32 81 | outBuffer: ptr Byte = nil 82 | outBufferSize: csize_t = 0 83 | 84 | for i in 0 ..< svnz.db.NumFiles: 85 | var 86 | offset: csize_t = 0 87 | outSizeProcessed: csize_t = 0 88 | length = SzArEx_GetFileNameUtf16(addr svnz.db, i.csize_t, nil) 89 | isDir = SzArEx_IsDir(svnz.db, i.int) 90 | 91 | if length > tempSize: 92 | SzFree(nil, temp) 93 | tempSize = length 94 | temp = cast[ptr UInt16](SzAlloc(nil, tempSize * sizeof(UInt16).csize_t)) 95 | if temp == nil: 96 | res = SZ_ERROR_MEM 97 | break 98 | 99 | discard SzArEx_GetFileNameUtf16(addr svnz.db, i.csize_t, temp) 100 | 101 | if isDir == 0: 102 | res = SzArEx_Extract(addr svnz.db, addr svnz.lookStream.vt, i.UInt32, addr blockIndex, 103 | addr outBuffer, addr outBufferSize, addr offset, addr outSizeProcessed, 104 | addr allocImp, addr allocTempImp) 105 | 106 | if res != SZ_OK: 107 | break 108 | 109 | yield ($cast[WideCString](temp), isDir, cast[ptr Byte](cast[csize_t](outBuffer)+offset), outSizeProcessed) 110 | 111 | checkError() 112 | 113 | proc extract*(svnz: SvnzFile, directory: string, skipOuterDirs=false, tempDir: string = "") = 114 | ## Extracts the files stored in the opened ``SvnzFile`` into the specified 115 | ## ``directory``. 116 | ## 117 | ## Options 118 | ## ------- 119 | ## 120 | ## ``skipOuterDirs`` - If ``true``, the archive's directory structure is not 121 | ## recreated; all files are deposited in the extraction directory. Similar to 122 | ## ``unzip``'s ``-j`` flag. 123 | 124 | # Create a temporary directory for us to extract into. This allows us to 125 | # implement the ``skipOuterDirs`` feature and ensures that no files are 126 | # extracted into the specified directory if the extraction fails mid-way. 127 | var tempDir = tempDir 128 | if tempDir.len() == 0: 129 | tempDir = getTempDir() / "nim7z-" & svnz.filename.extractFilename() 130 | removeDir(tempDir) 131 | createDir(tempDir) 132 | 133 | svnz.loadDataStream() 134 | 135 | for file, isDir, data, size in svnz.walk(): 136 | if isDir == 0: 137 | let fp = (tempDir / (if not skipOuterDirs: file else: file.extractFilename())).open(fmWrite) 138 | let arr = cast[ptr UncheckedArray[char]](data) 139 | for i in 0 ..< size: 140 | write(fp, arr[i]) 141 | fp.close() 142 | else: 143 | if not skipOuterDirs: 144 | createDir(tempDir / file) 145 | 146 | # Determine which directory to copy. 147 | var srcDir = tempDir 148 | let contents = toSeq(walkDir(srcDir)) 149 | if contents.len == 1 and skipOuterDirs: 150 | # Skip the outer directory. 151 | srcDir = contents[0][1] 152 | 153 | # Finally copy the directory to what the user specified. 154 | copyDir(srcDir, directory) 155 | removeDir(tempDir) 156 | 157 | proc extract*(svnzf, directory: string, skipOuterDirs=false, tempDir: string = "") = 158 | ## Extracts the files stored in the file name specified, to the directory specified. 159 | let svnz = new7zFile(svnzf) 160 | extract(svnz, directory, skipOuterDirs, tempDir) 161 | --------------------------------------------------------------------------------