2011-07-14 12:34:53 -06:00
|
|
|
// Copyright 2011 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// This file provides an implementation of the FileSystem
|
|
|
|
// interface based on the contents of a .zip file.
|
|
|
|
//
|
|
|
|
// Assumptions:
|
|
|
|
//
|
|
|
|
// - The file paths stored in the zip file must use a slash ('/') as path
|
|
|
|
// separator; and they must be relative (i.e., they must not start with
|
|
|
|
// a '/' - this is usually the case if the file was created w/o special
|
|
|
|
// options).
|
|
|
|
// - The zip file system treats the file paths found in the zip internally
|
|
|
|
// like absolute paths w/o a leading '/'; i.e., the paths are considered
|
|
|
|
// relative to the root of the file system.
|
|
|
|
// - All path arguments to file system methods must be absolute paths.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"archive/zip"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"path"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// zipFI is the zip-file based implementation of FileInfo
|
|
|
|
type zipFI struct {
|
|
|
|
name string // directory-local name
|
|
|
|
file *zip.File // nil for a directory
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fi zipFI) Name() string {
|
|
|
|
return fi.name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fi zipFI) Size() int64 {
|
2011-07-19 09:22:20 -06:00
|
|
|
if f := fi.file; f != nil {
|
|
|
|
return int64(f.UncompressedSize)
|
2011-07-14 12:34:53 -06:00
|
|
|
}
|
|
|
|
return 0 // directory
|
|
|
|
}
|
|
|
|
|
2011-07-19 09:22:20 -06:00
|
|
|
func (fi zipFI) Mtime_ns() int64 {
|
|
|
|
if f := fi.file; f != nil {
|
|
|
|
return f.Mtime_ns()
|
|
|
|
}
|
|
|
|
return 0 // directory has no modified time entry
|
|
|
|
}
|
|
|
|
|
2011-07-14 12:34:53 -06:00
|
|
|
func (fi zipFI) IsDirectory() bool {
|
|
|
|
return fi.file == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fi zipFI) IsRegular() bool {
|
|
|
|
return fi.file != nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// zipFS is the zip-file based implementation of FileSystem
|
|
|
|
type zipFS struct {
|
|
|
|
*zip.ReadCloser
|
|
|
|
list zipList
|
|
|
|
}
|
|
|
|
|
2011-11-01 20:06:05 -06:00
|
|
|
func (fs *zipFS) Close() error {
|
2011-07-14 12:34:53 -06:00
|
|
|
fs.list = nil
|
|
|
|
return fs.ReadCloser.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func zipPath(name string) string {
|
2011-07-20 17:22:13 -06:00
|
|
|
name = path.Clean(name)
|
2011-07-14 12:34:53 -06:00
|
|
|
if !path.IsAbs(name) {
|
|
|
|
panic(fmt.Sprintf("stat: not an absolute path: %s", name))
|
|
|
|
}
|
2011-07-20 17:22:13 -06:00
|
|
|
return name[1:] // strip leading '/'
|
2011-07-14 12:34:53 -06:00
|
|
|
}
|
|
|
|
|
2011-11-01 20:06:05 -06:00
|
|
|
func (fs *zipFS) stat(abspath string) (int, zipFI, error) {
|
2011-07-20 17:22:13 -06:00
|
|
|
i, exact := fs.list.lookup(abspath)
|
2011-07-14 12:34:53 -06:00
|
|
|
if i < 0 {
|
2011-07-27 16:14:12 -06:00
|
|
|
// abspath has leading '/' stripped - print it explicitly
|
|
|
|
return -1, zipFI{}, fmt.Errorf("file not found: /%s", abspath)
|
2011-07-14 12:34:53 -06:00
|
|
|
}
|
2011-07-20 17:22:13 -06:00
|
|
|
_, name := path.Split(abspath)
|
2011-07-14 12:34:53 -06:00
|
|
|
var file *zip.File
|
2011-07-20 17:22:13 -06:00
|
|
|
if exact {
|
2011-07-14 12:34:53 -06:00
|
|
|
file = fs.list[i] // exact match found - must be a file
|
|
|
|
}
|
|
|
|
return i, zipFI{name, file}, nil
|
|
|
|
}
|
|
|
|
|
2011-11-01 20:06:05 -06:00
|
|
|
func (fs *zipFS) Open(abspath string) (io.ReadCloser, error) {
|
2011-07-14 12:34:53 -06:00
|
|
|
_, fi, err := fs.stat(zipPath(abspath))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if fi.IsDirectory() {
|
|
|
|
return nil, fmt.Errorf("Open: %s is a directory", abspath)
|
|
|
|
}
|
|
|
|
return fi.file.Open()
|
|
|
|
}
|
|
|
|
|
2011-11-01 20:06:05 -06:00
|
|
|
func (fs *zipFS) Lstat(abspath string) (FileInfo, error) {
|
2011-07-14 12:34:53 -06:00
|
|
|
_, fi, err := fs.stat(zipPath(abspath))
|
|
|
|
return fi, err
|
|
|
|
}
|
|
|
|
|
2011-11-01 20:06:05 -06:00
|
|
|
func (fs *zipFS) Stat(abspath string) (FileInfo, error) {
|
2011-07-14 12:34:53 -06:00
|
|
|
_, fi, err := fs.stat(zipPath(abspath))
|
|
|
|
return fi, err
|
|
|
|
}
|
|
|
|
|
2011-11-01 20:06:05 -06:00
|
|
|
func (fs *zipFS) ReadDir(abspath string) ([]FileInfo, error) {
|
2011-07-14 12:34:53 -06:00
|
|
|
path := zipPath(abspath)
|
|
|
|
i, fi, err := fs.stat(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !fi.IsDirectory() {
|
|
|
|
return nil, fmt.Errorf("ReadDir: %s is not a directory", abspath)
|
|
|
|
}
|
|
|
|
|
|
|
|
var list []FileInfo
|
|
|
|
dirname := path + "/"
|
|
|
|
prevname := ""
|
|
|
|
for _, e := range fs.list[i:] {
|
|
|
|
if !strings.HasPrefix(e.Name, dirname) {
|
|
|
|
break // not in the same directory anymore
|
|
|
|
}
|
|
|
|
name := e.Name[len(dirname):] // local name
|
|
|
|
file := e
|
|
|
|
if i := strings.IndexRune(name, '/'); i >= 0 {
|
|
|
|
// We infer directories from files in subdirectories.
|
|
|
|
// If we have x/y, return a directory entry for x.
|
|
|
|
name = name[0:i] // keep local directory name only
|
|
|
|
file = nil
|
|
|
|
}
|
|
|
|
// If we have x/y and x/z, don't return two directory entries for x.
|
|
|
|
// TODO(gri): It should be possible to do this more efficiently
|
|
|
|
// by determining the (fs.list) range of local directory entries
|
|
|
|
// (via two binary searches).
|
|
|
|
if name != prevname {
|
|
|
|
list = append(list, zipFI{name, file})
|
|
|
|
prevname = name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return list, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewZipFS(rc *zip.ReadCloser) FileSystem {
|
|
|
|
list := make(zipList, len(rc.File))
|
|
|
|
copy(list, rc.File) // sort a copy of rc.File
|
|
|
|
sort.Sort(list)
|
|
|
|
return &zipFS{rc, list}
|
|
|
|
}
|
|
|
|
|
|
|
|
type zipList []*zip.File
|
|
|
|
|
|
|
|
// zipList implements sort.Interface
|
|
|
|
func (z zipList) Len() int { return len(z) }
|
|
|
|
func (z zipList) Less(i, j int) bool { return z[i].Name < z[j].Name }
|
|
|
|
func (z zipList) Swap(i, j int) { z[i], z[j] = z[j], z[i] }
|
|
|
|
|
2011-07-20 17:22:13 -06:00
|
|
|
// lookup returns the smallest index of an entry with an exact match
|
|
|
|
// for name, or an inexact match starting with name/. If there is no
|
|
|
|
// such entry, the result is -1, false.
|
|
|
|
func (z zipList) lookup(name string) (index int, exact bool) {
|
|
|
|
// look for exact match first (name comes before name/ in z)
|
2011-07-14 12:34:53 -06:00
|
|
|
i := sort.Search(len(z), func(i int) bool {
|
|
|
|
return name <= z[i].Name
|
|
|
|
})
|
2011-08-29 17:49:31 -06:00
|
|
|
if i >= len(z) {
|
2011-07-20 17:22:13 -06:00
|
|
|
return -1, false
|
|
|
|
}
|
2011-08-29 17:49:31 -06:00
|
|
|
// 0 <= i < len(z)
|
2011-07-20 17:22:13 -06:00
|
|
|
if z[i].Name == name {
|
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// look for inexact match (must be in z[i:], if present)
|
|
|
|
z = z[i:]
|
|
|
|
name += "/"
|
|
|
|
j := sort.Search(len(z), func(i int) bool {
|
|
|
|
return name <= z[i].Name
|
|
|
|
})
|
2011-08-29 17:49:31 -06:00
|
|
|
if j >= len(z) {
|
2011-07-20 17:22:13 -06:00
|
|
|
return -1, false
|
2011-07-14 12:34:53 -06:00
|
|
|
}
|
2011-08-29 17:49:31 -06:00
|
|
|
// 0 <= j < len(z)
|
2011-07-22 16:21:50 -06:00
|
|
|
if strings.HasPrefix(z[j].Name, name) {
|
|
|
|
return i + j, false
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1, false
|
2011-07-14 12:34:53 -06:00
|
|
|
}
|