github.com/kjk/siser@v0.0.0-20220410204903-1b1e84ea1397/pak/writer.go (about) 1 package pak 2 3 import ( 4 "bytes" 5 "crypto/sha1" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "strconv" 12 "time" 13 14 "github.com/kjk/siser" 15 ) 16 17 const ( 18 // MetaKeyPath is name of mandatory "Path" meta-value 19 MetaKeyPath = "Path" 20 // MetaKeySize is name of mandatory "Size" meta-value 21 MetaKeySize = "Size" 22 // MetaKeySha1 is name of mandatory "Sha1" meta-value 23 MetaKeySha1 = "Sha1" 24 25 archiveName = "pak-archive2" 26 archiveEntryName = "pak-entry" 27 ) 28 29 // Writer is for creating an archive 30 type Writer struct { 31 // Entries is exposed so that we can re-arrange (e.g. sort) 32 // them before calling Write 33 Entries []*Entry 34 35 // TODO: add option to conserve memory when writing 36 } 37 38 // NewWriter creates a new archive writer 39 func NewWriter() *Writer { 40 return &Writer{} 41 } 42 43 func getFileSize(path string) (int64, error) { 44 fi, err := os.Lstat(path) 45 if err != nil { 46 return 0, err 47 } 48 return fi.Size(), nil 49 } 50 51 func sha1HexOfBytes(d []byte) string { 52 h := sha1.New() 53 h.Write(d) 54 return fmt.Sprintf("%x", h.Sum(nil)) 55 } 56 57 // AddFile adds a file from disk to the archive. If meta has "Path" 58 // value, it'll over-write path of the file in meta-data 59 func (w *Writer) AddFile(path string, meta Metadata) error { 60 size, err := getFileSize(path) 61 if err != nil { 62 return err 63 } 64 65 // TODO: an option that preserves memory i.e. doesn't keep 66 // data in memory. It'll be slower because it'll have to 67 // read files twice 68 d, err := ioutil.ReadFile(path) 69 if err != nil { 70 return err 71 } 72 73 e := &Entry{ 74 srcFilePath: path, 75 data: d, 76 Path: path, 77 Size: size, 78 Sha1: sha1HexOfBytes(d), 79 Metadata: meta, 80 } 81 82 // if "Path" is in meta-data, it over-writes physical path 83 if v, ok := meta.Get("Path"); ok { 84 if v == "" { 85 return ErrNoPath 86 } 87 e.Path = v 88 } 89 90 w.Entries = append(w.Entries, e) 91 return nil 92 } 93 94 // AddData adds a file from disk to the archive 95 func (w *Writer) AddData(d []byte, path string, meta Metadata) error { 96 if path == "" { 97 return ErrNoPath 98 } 99 sha1 := sha1HexOfBytes(d) 100 e := &Entry{ 101 data: d, 102 Path: path, 103 Size: int64(len(d)), 104 Sha1: sha1, 105 Metadata: meta, 106 } 107 w.Entries = append(w.Entries, e) 108 return nil 109 } 110 111 func serializeHeader(entries []*Entry) ([]byte, error) { 112 var buf bytes.Buffer 113 sw := siser.NewWriter(&buf) 114 115 var r siser.Record 116 for _, e := range entries { 117 r.Reset() 118 119 meta := e.Metadata 120 meta.Set(MetaKeyPath, e.Path) 121 meta.Set(MetaKeySize, strconv.FormatInt(e.Size, 10)) 122 meta.Set(MetaKeySha1, e.Sha1) 123 124 for _, kv := range meta.Meta { 125 r.Write(kv.Key, kv.Value) 126 } 127 128 r.Name = archiveEntryName 129 130 _, err := sw.WriteRecord(&r) 131 if err != nil { 132 return nil, err 133 } 134 } 135 return buf.Bytes(), nil 136 } 137 138 // WriteToFile writes an archive to a file 139 func (w *Writer) WriteToFile(path string) error { 140 f, err := os.Create(path) 141 if err != nil { 142 return err 143 } 144 err = w.Write(f) 145 err2 := f.Close() 146 147 if err != nil || err2 != nil { 148 os.Remove(path) 149 if err != nil { 150 return err 151 } 152 return err2 153 } 154 return nil 155 } 156 157 // Write writes an archive to a writer 158 func (w *Writer) Write(wr io.Writer) (err error) { 159 if wr == nil { 160 return errors.New("must provide io.Writer to NewArchiveWriter") 161 } 162 163 if len(w.Entries) == 0 { 164 return errors.New("there are 0 entries to write") 165 } 166 167 hdr, err := serializeHeader(w.Entries) 168 if err != nil { 169 return err 170 } 171 172 sw := siser.NewWriter(wr) 173 if _, err = sw.Write(hdr, time.Now(), archiveName); err != nil { 174 return err 175 } 176 177 // write files at the end of the archive 178 for _, e := range w.Entries { 179 d := e.data 180 if d == nil && e.srcFilePath != "" { 181 d2, err := ioutil.ReadFile(e.srcFilePath) 182 if err != nil { 183 return err 184 } 185 d = d2 186 } 187 188 if len(d) == 0 { 189 // it's ok to have empty files 190 continue 191 } 192 193 if _, err = wr.Write(d); err != nil { 194 return err 195 } 196 } 197 return nil 198 }