github.com/kjk/siser@v0.0.0-20220410204903-1b1e84ea1397/pak/README.md (about) 1 ## What is `pak` 2 3 `pak` is a go library for creating an archive (like a `.zip` archive). 4 5 Use it when you need to bundle multiple files as a single file. 6 7 Why not just use a `.zip`? 8 9 The difference between a `.zip` and `pak` archive: 10 11 - `pak` supports arbitrary file metadata as key / value pairs 12 - `pak` doesn't compress by itself but you can use any compression type you like (record the compression type in metadata) 13 - `pak` archive header is at the beginning of the file and the format is optimized for random access to files 14 15 What are the limitations? It's meant for 'create once, read multiple times'. It doesn't support updating archives after creation. 16 17 ## Create `pak` archive 18 19 ```go 20 a := pak.NewWriter() 21 meta := pak.Metadata{} 22 // optional, add arbitrary meta-data about the file 23 meta.Set("Type", "text file") 24 // add a file "foo.txt" to the archive 25 err := a.AddFile("foo.txt", meta) 26 if err != nil { 27 log.Fatal("failed to add a file\n") 28 } 29 30 // add data with name "bar.txt" 31 err := a.AddData([]byte("content of the file", "bar.txt", pak.Metadata{})) 32 if err != nil { 33 log.Fatal("failed to add a file\n") 34 } 35 // ... add more files 36 37 // write creates an archive 38 err = a.WriteToFile("myarchive.pak") 39 if err != nil { 40 log.Fatal("failed to write an archive\n") 41 } 42 // alternatively, write to any io.Writer: 43 // var wr io.Writer = ... 44 // err = w.Write(wr) 45 ``` 46 47 ## Read `pak` archive 48 49 ```go 50 archive, err := pak.ReadArchive("myarchive.pak") 51 if err != nil { 52 log.Fatalf("failed to open an archive with '%s'\n", err) 53 } 54 defer f.Close() 55 entries := archive.Entries 56 fmt.Printf("Archive has %d entries\n", len(entries)) 57 entry := entries[0] 58 d, err := archive.ReadEntry(entry) 59 if err != nil { 60 log.Fatalf("archive.ReadEntry() failed with '%s'\n", err) 61 } 62 // alternatively, you can use entry.Offset and entry.Size to read the data 63 64 // print metadata entries 65 for _, m := entry.Metadata.Meta { 66 fmt.Printf("metadata entry: key='%s', value='%s'\n", m.Key, m.Value) 67 } 68 ``` 69 70 ## More on motivation 71 72 I wanted to package assets (.html files etc.) to be served via a web server into a single file. 73 74 Usually I would `.zip` file for that but `ZIP` is an old, quirky file format. 75 76 I wanted a simple and fast way to send a file out of the archive to the browser, already pre-compressed with a browser-compatible compression algorithm like brotli. 77 78 Zip doesn't natively support brotli compression. Not an unfixable problem (zip allows to store uncompressed files) but I took this opportunity to implement my own archive format which supports arbitrary per-file meta-data. 79 80 Building on top of my [siser](https://github.com/kjk/siser) library it's under 400 lines of code. 81 82 The format is very simple: the header at the beginning of the file contains a list of files with their mata-data and position in the archive. 83 84 The header is stored in [siser](https://github.com/kjk/siser) format. 85 86 The data for each file follows. 87 88 The archive is meant for batch creation. It doesn't support adding/removing/updating of files after the archive has been created.