114 lines
2.3 KiB
Go
114 lines
2.3 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"archive/zip"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// Zip walks `src`, omitting `trim`, writing to `w`
|
||
|
func Zip(w io.Writer, src string, trim string) error {
|
||
|
zw := zip.NewWriter(w)
|
||
|
defer zw.Close()
|
||
|
|
||
|
return filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
|
||
|
// path includes fi.Name() already
|
||
|
if nil != err {
|
||
|
fmt.Println("warning: skipped", path+": ", err)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
zipOne(zw, path, fi, trim)
|
||
|
return nil
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func zipOne(zw *zip.Writer, path string, fi os.FileInfo, trim string) error {
|
||
|
h, err := zip.FileInfoHeader(fi)
|
||
|
if nil != err {
|
||
|
return err
|
||
|
}
|
||
|
h.Name = strings.TrimPrefix(strings.TrimPrefix(path, trim), string(filepath.Separator))
|
||
|
|
||
|
if fi.IsDir() {
|
||
|
fmt.Printf("directory: %s\n\t%q\n", path, h.Name)
|
||
|
return zipDirectory(zw, h)
|
||
|
}
|
||
|
|
||
|
// Allow zipping a single file
|
||
|
if "" == h.Name {
|
||
|
h.Name = path
|
||
|
}
|
||
|
if fi.Mode().IsRegular() {
|
||
|
fmt.Printf("file: %s\n\t%q\n", path, h.Name)
|
||
|
return zipFile(zw, h, path)
|
||
|
}
|
||
|
|
||
|
if os.ModeSymlink == (fi.Mode() & os.ModeType) {
|
||
|
fmt.Printf("symlink: %s\n\t%q\n", path, h.Name)
|
||
|
return zipSymlink(zw, h, path)
|
||
|
}
|
||
|
|
||
|
fmt.Printf("skipping: %s\n\t(irregular file type)\n", path)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func zipDirectory(zw *zip.Writer, h *zip.FileHeader) error {
|
||
|
// directories must end in / for go
|
||
|
h.Name = strings.TrimPrefix(h.Name+"/", "/")
|
||
|
|
||
|
// skip top-level, trimmed directory
|
||
|
if "" == h.Name {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if _, err := zw.CreateHeader(h); nil != err {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func zipFile(zw *zip.Writer, h *zip.FileHeader, path string) error {
|
||
|
r, err := os.Open(path)
|
||
|
if nil != err {
|
||
|
return err
|
||
|
}
|
||
|
defer r.Close()
|
||
|
|
||
|
// Files should be zipped (not dirs, and symlinks... meh)
|
||
|
// TODO investigate if files below a certain size shouldn't be deflated
|
||
|
h.Method = zip.Deflate
|
||
|
w, err := zw.CreateHeader(h)
|
||
|
if nil != err {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if _, err := io.Copy(w, r); nil != err {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func zipSymlink(zw *zip.Writer, h *zip.FileHeader, path string) error {
|
||
|
w, err := zw.CreateHeader(h)
|
||
|
if nil != err {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// TODO make sure that this is within the root directory
|
||
|
targetpath, err := os.Readlink(path)
|
||
|
if nil != err {
|
||
|
return err
|
||
|
}
|
||
|
if _, err := w.Write([]byte(targetpath)); nil != err {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|