diff --git a/src/instance.go b/src/instance.go index 1d3a26f..0741ca0 100644 --- a/src/instance.go +++ b/src/instance.go @@ -22,7 +22,7 @@ type Instance struct { ThemeDir string AttemptInterval time.Duration - categories map[string]*Mothball + categories map[string]*Zipfs update chan bool jPuzzleList []byte jPointsLog []byte @@ -41,7 +41,7 @@ func (ctx *Instance) Initialize() error { } ctx.Base = strings.TrimRight(ctx.Base, "/") - ctx.categories = map[string]*Mothball{} + ctx.categories = map[string]*Zipfs{} ctx.update = make(chan bool, 10) ctx.nextAttempt = map[string]time.Time{} ctx.nextAttemptMutex = new(sync.RWMutex) diff --git a/src/maintenance.go b/src/maintenance.go index abd51e0..751271b 100644 --- a/src/maintenance.go +++ b/src/maintenance.go @@ -148,7 +148,7 @@ func (ctx *Instance) tidy() { categoryName := strings.TrimSuffix(filename, ".mb") if _, ok := ctx.categories[categoryName]; !ok { - mb, err := OpenMothball(filepath) + mb, err := OpenZipfs(filepath) if err != nil { log.Printf("Error opening %s: %s", filepath, err) continue diff --git a/src/mothball.go b/src/mothball.go deleted file mode 100644 index 149dbf5..0000000 --- a/src/mothball.go +++ /dev/null @@ -1,191 +0,0 @@ -package main - -import ( - "archive/zip" - "fmt" - "io" - "io/ioutil" - "os" - "strings" - "time" -) - -type Mothball struct { - zf *zip.ReadCloser - filename string - mtime time.Time -} - -type MothballFile struct { - f io.ReadCloser - pos int64 - zf *zip.File - io.Reader - io.Seeker - io.Closer -} - -func NewMothballFile(zf *zip.File) (*MothballFile, error) { - mf := &MothballFile{ - zf: zf, - pos: 0, - f: nil, - } - if err := mf.reopen(); err != nil { - return nil, err - } - return mf, nil -} - -func (mf *MothballFile) reopen() error { - if mf.f != nil { - if err := mf.f.Close(); err != nil { - return err - } - } - f, err := mf.zf.Open() - if err != nil { - return err - } - mf.f = f - mf.pos = 0 - return nil -} - -func (mf *MothballFile) ModTime() time.Time { - return mf.zf.Modified -} - -func (mf *MothballFile) Read(p []byte) (int, error) { - n, err := mf.f.Read(p) - mf.pos += int64(n) - return n, err -} - -func (mf *MothballFile) Seek(offset int64, whence int) (int64, error) { - var pos int64 - switch whence { - case io.SeekStart: - pos = offset - case io.SeekCurrent: - pos = mf.pos + int64(offset) - case io.SeekEnd: - pos = int64(mf.zf.UncompressedSize64) - int64(offset) - } - - if pos < 0 { - return mf.pos, fmt.Errorf("Tried to seek %d before start of file", pos) - } - if pos >= int64(mf.zf.UncompressedSize64) { - // We don't need to decompress anything, we're at the end of the file - mf.f.Close() - mf.f = ioutil.NopCloser(strings.NewReader("")) - mf.pos = int64(mf.zf.UncompressedSize64) - return mf.pos, nil - } - if pos < mf.pos { - if err := mf.reopen(); err != nil { - return mf.pos, err - } - } - - buf := make([]byte, 32*1024) - for pos > mf.pos { - l := pos - mf.pos - if l > int64(cap(buf)) { - l = int64(cap(buf)) - 1 - } - p := buf[0:int(l)] - n, err := mf.Read(p) - if err != nil { - return mf.pos, err - } else if n <= 0 { - return mf.pos, fmt.Errorf("Short read (%d bytes)", n) - } - } - - return mf.pos, nil -} - -func (mf *MothballFile) Close() error { - return mf.f.Close() -} - -func OpenMothball(filename string) (*Mothball, error) { - var m Mothball - - m.filename = filename - - err := m.Refresh() - if err != nil { - return nil, err - } - - return &m, nil -} - -func (m *Mothball) Close() error { - return m.zf.Close() -} - -func (m *Mothball) Refresh() error { - info, err := os.Stat(m.filename) - if err != nil { - return err - } - mtime := info.ModTime() - - if !mtime.After(m.mtime) { - return nil - } - - zf, err := zip.OpenReader(m.filename) - if err != nil { - return err - } - - if m.zf != nil { - m.zf.Close() - } - m.zf = zf - m.mtime = mtime - - return nil -} - -func (m *Mothball) get(filename string) (*zip.File, error) { - for _, f := range m.zf.File { - if filename == f.Name { - return f, nil - } - } - return nil, fmt.Errorf("File not found: %s %s", m.filename, filename) -} - -func (m *Mothball) Header(filename string) (*zip.FileHeader, error) { - f, err := m.get(filename) - if err != nil { - return nil, err - } - return &f.FileHeader, nil -} - -func (m *Mothball) Open(filename string) (*MothballFile, error) { - f, err := m.get(filename) - if err != nil { - return nil, err - } - mf, err := NewMothballFile(f) - return mf, err -} - -func (m *Mothball) ReadFile(filename string) ([]byte, error) { - f, err := m.Open(filename) - if err != nil { - return nil, err - } - defer f.Close() - - bytes, err := ioutil.ReadAll(f) - return bytes, err -} diff --git a/src/zipfs.go b/src/zipfs.go new file mode 100644 index 0000000..1b0f00a --- /dev/null +++ b/src/zipfs.go @@ -0,0 +1,191 @@ +package main + +import ( + "archive/zip" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + "time" +) + +type Zipfs struct { + zf *zip.ReadCloser + filename string + mtime time.Time +} + +type ZipfsFile struct { + f io.ReadCloser + pos int64 + zf *zip.File + io.Reader + io.Seeker + io.Closer +} + +func NewZipfsFile(zf *zip.File) (*ZipfsFile, error) { + zfsf := &ZipfsFile{ + zf: zf, + pos: 0, + f: nil, + } + if err := zfsf.reopen(); err != nil { + return nil, err + } + return zfsf, nil +} + +func (zfsf *ZipfsFile) reopen() error { + if zfsf.f != nil { + if err := zfsf.f.Close(); err != nil { + return err + } + } + f, err := zfsf.zf.Open() + if err != nil { + return err + } + zfsf.f = f + zfsf.pos = 0 + return nil +} + +func (zfsf *ZipfsFile) ModTime() time.Time { + return zfsf.zf.Modified +} + +func (zfsf *ZipfsFile) Read(p []byte) (int, error) { + n, err := zfsf.f.Read(p) + zfsf.pos += int64(n) + return n, err +} + +func (zfsf *ZipfsFile) Seek(offset int64, whence int) (int64, error) { + var pos int64 + switch whence { + case io.SeekStart: + pos = offset + case io.SeekCurrent: + pos = zfsf.pos + int64(offset) + case io.SeekEnd: + pos = int64(zfsf.zf.UncompressedSize64) - int64(offset) + } + + if pos < 0 { + return zfsf.pos, fmt.Errorf("Tried to seek %d before start of file", pos) + } + if pos >= int64(zfsf.zf.UncompressedSize64) { + // We don't need to decompress anything, we're at the end of the file + zfsf.f.Close() + zfsf.f = ioutil.NopCloser(strings.NewReader("")) + zfsf.pos = int64(zfsf.zf.UncompressedSize64) + return zfsf.pos, nil + } + if pos < zfsf.pos { + if err := zfsf.reopen(); err != nil { + return zfsf.pos, err + } + } + + buf := make([]byte, 32*1024) + for pos > zfsf.pos { + l := pos - zfsf.pos + if l > int64(cap(buf)) { + l = int64(cap(buf)) - 1 + } + p := buf[0:int(l)] + n, err := zfsf.Read(p) + if err != nil { + return zfsf.pos, err + } else if n <= 0 { + return zfsf.pos, fmt.Errorf("Short read (%d bytes)", n) + } + } + + return zfsf.pos, nil +} + +func (zfsf *ZipfsFile) Close() error { + return zfsf.f.Close() +} + +func OpenZipfs(filename string) (*Zipfs, error) { + var zfs Zipfs + + zfs.filename = filename + + err := zfs.Refresh() + if err != nil { + return nil, err + } + + return &zfs, nil +} + +func (zfs *Zipfs) Close() error { + return zfs.zf.Close() +} + +func (zfs *Zipfs) Refresh() error { + info, err := os.Stat(zfs.filename) + if err != nil { + return err + } + mtime := info.ModTime() + + if !mtime.After(zfs.mtime) { + return nil + } + + zf, err := zip.OpenReader(zfs.filename) + if err != nil { + return err + } + + if zfs.zf != nil { + zfs.zf.Close() + } + zfs.zf = zf + zfs.mtime = mtime + + return nil +} + +func (zfs *Zipfs) get(filename string) (*zip.File, error) { + for _, f := range zfs.zf.File { + if filename == f.Name { + return f, nil + } + } + return nil, fmt.Errorf("File not found: %s %s", zfs.filename, filename) +} + +func (zfs *Zipfs) Header(filename string) (*zip.FileHeader, error) { + f, err := zfs.get(filename) + if err != nil { + return nil, err + } + return &f.FileHeader, nil +} + +func (zfs *Zipfs) Open(filename string) (*ZipfsFile, error) { + f, err := zfs.get(filename) + if err != nil { + return nil, err + } + zfsf, err := NewZipfsFile(f) + return zfsf, err +} + +func (zfs *Zipfs) ReadFile(filename string) ([]byte, error) { + f, err := zfs.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + + bytes, err := ioutil.ReadAll(f) + return bytes, err +} diff --git a/src/mothball_test.go b/src/zipfs_test.go similarity index 87% rename from src/mothball_test.go rename to src/zipfs_test.go index 8115809..22d9386 100644 --- a/src/mothball_test.go +++ b/src/zipfs_test.go @@ -9,8 +9,8 @@ import ( "testing" ) -func TestMothball(t *testing.T) { - tf, err := ioutil.TempFile("", "mothball") +func TestZipfs(t *testing.T) { + tf, err := ioutil.TempFile("", "zipfs") if err != nil { t.Error(err) return @@ -35,7 +35,7 @@ func TestMothball(t *testing.T) { tf.Close() // Now read it in - mb, err := OpenMothball(tf.Name()) + mb, err := OpenZipfs(tf.Name()) if err != nil { t.Error(err) return